diff --git a/README.md b/README.md index b4afb10..0a493ce 100644 --- a/README.md +++ b/README.md @@ -94,8 +94,7 @@ docker compose up docker-compose restart # Остановка сервисов -docker compose down - +docker-compose-down ## Контакты и поддержка diff --git a/backend/app.js b/backend/app.js index 8cd364a..a1b4061 100644 --- a/backend/app.js +++ b/backend/app.js @@ -45,14 +45,11 @@ const ensureDirectoriesExist = () => { for (const dir of directories) { if (!fs.existsSync(dir)) { try { - logger.info(`Создание директории: ${dir}`); fs.mkdirSync(dir, { recursive: true }); - logger.info(`Директория успешно создана: ${dir}`); } catch (error) { logger.error(`Ошибка при создании директории ${dir}: ${error.message}`); } } else { - logger.info(`Директория существует: ${dir}`); } // Проверка прав на запись @@ -60,7 +57,6 @@ const ensureDirectoriesExist = () => { const testFile = path.join(dir, '.write-test'); fs.writeFileSync(testFile, 'test'); fs.unlinkSync(testFile); - logger.info(`Директория доступна для записи: ${dir}`); } catch (error) { logger.error(`Директория ${dir} недоступна для записи: ${error.message}`); } @@ -137,47 +133,19 @@ app.use(async (req, res, next) => { const result = await db.getQuery()('SELECT sess FROM session WHERE sid = $1', [req.sessionID]); // console.log('Session from DB:', result.rows[0]?.sess); } - - // Если сессия уже есть, используем её - if (req.session.authenticated) { - next(); - return; - } - - // Проверяем заголовок авторизации - const authHeader = req.headers.authorization; - if (authHeader && authHeader.startsWith('Bearer ')) { - const token = authHeader.split(' ')[1]; - try { - // Находим пользователя по токену - const { rows } = await db.getQuery( - ` - SELECT u.id, - (u.role = 'admin') as is_admin, - u.address - FROM users u - WHERE u.id = $1 - `, - [token] - ); - - if (rows.length > 0) { - const user = rows[0]; - req.session.userId = user.id; - req.session.address = user.address; - req.session.isAdmin = user.is_admin; - req.session.authenticated = true; - - await new Promise((resolve) => req.session.save(resolve)); - } - } catch (error) { - // console.error('Error checking auth header:', error); - } - } - + next(); }); +// Логирование запросов (только для отладки) +if (process.env.NODE_ENV === 'development') { + app.use((req, res, next) => { + // console.log('[APP] Глобальный лог:', req.method, req.originalUrl); // Убрано + // logger.info(`${req.method} ${req.url}`); // Убрано + next(); + }); +} + // Middleware для подстановки req.user из сессии app.use((req, res, next) => { if (req.session && req.session.userId) { @@ -205,13 +173,6 @@ app.use( }) ); -// Логирование запросов -app.use((req, res, next) => { - // console.log('[APP] Глобальный лог:', req.method, req.originalUrl); - logger.info(`${req.method} ${req.url}`); - next(); -}); - // Маршруты API app.use('/api/tables', tablesRoutes); // ДОЛЖНО БЫТЬ ВЫШЕ! // app.use('/api', identitiesRoutes); @@ -341,4 +302,20 @@ setInterval( 15 * 60 * 1000 ); // Каждые 15 минут +// Инициализация сервиса настроек БД для динамического обновления подключений +const initializeDbSettingsService = async () => { + try { + const dbSettingsService = require('./services/dbSettingsService'); + await dbSettingsService.initialize(); + // logger.info('[App] Сервис настроек БД инициализирован'); // Убрано избыточное логирование + } catch (error) { + logger.error('[App] Ошибка инициализации сервиса настроек БД:', error); + } +}; + +// Инициализируем сервис настроек БД при запуске +if (process.env.NODE_ENV !== 'migration') { + initializeDbSettingsService(); +} + module.exports = { app, nonceStore }; diff --git a/backend/db.js b/backend/db.js index db9a8a0..e0c2ed8 100644 --- a/backend/db.js +++ b/backend/db.js @@ -14,13 +14,13 @@ const { Pool } = require('pg'); require('dotenv').config(); const axios = require('axios'); -// Выводим настройки подключения (без пароля) -console.log('Настройки подключения к базе данных:'); -console.log('DATABASE_URL:', process.env.DATABASE_URL?.replace(/:([^:@]+)@/, ':***@')); -console.log('DB_HOST:', process.env.DB_HOST); -console.log('DB_PORT:', process.env.DB_PORT); -console.log('DB_NAME:', process.env.DB_NAME); -console.log('DB_USER:', process.env.DB_USER); +// Убираем избыточное логирование настроек подключения +// console.log('Настройки подключения к базе данных:'); +// console.log('DATABASE_URL:', process.env.DATABASE_URL?.replace(/:([^:@]+)@/, ':***@')); +// console.log('DB_HOST:', process.env.DB_HOST); +// console.log('DB_PORT:', process.env.DB_PORT); +// console.log('DB_NAME:', process.env.DB_NAME); +// console.log('DB_USER:', process.env.DB_USER); // Первичное подключение по дефолтным значениям let pool = new Pool({ @@ -50,23 +50,23 @@ pool.on('error', (err) => { // Обработчик для очистки при завершении процесса process.on('SIGINT', async () => { - console.log('Closing database pool...'); + // console.log('Closing database pool...'); // Убрано избыточное логирование await pool.end(); process.exit(0); }); process.on('SIGTERM', async () => { - console.log('Closing database pool...'); + // console.log('Closing database pool...'); // Убрано избыточное логирование await pool.end(); process.exit(0); }); -console.log('Пул создан:', pool.options || pool); +// console.log('Пул создан:', pool.options || pool); // Убрано избыточное логирование // Проверяем подключение к базе данных pool.query('SELECT NOW()') .then(res => { - console.log('Успешное подключение к базе данных:', res.rows[0]); + // console.log('Успешное подключение к базе данных:', res.rows[0]); // Убрано избыточное логирование }) .catch(err => { console.error('Ошибка подключения к базе данных:', err); @@ -203,7 +203,7 @@ async function saveGuestMessageToDatabase(message, language, guestId) { `, [guestId, message, language] ); - console.log('Гостевое сообщение успешно сохранено:', message); + // console.log('Гостевое сообщение успешно сохранено:', message); // Убрано избыточное логирование } catch (error) { console.error('Ошибка при сохранении гостевого сообщения:', error); throw error; // Пробрасываем ошибку дальше @@ -219,9 +219,9 @@ async function waitForOllamaModel(modelName) { if (models.includes(modelName)) { return true; } - console.log(`[seedAIAssistantSettings] Ожидание загрузки модели ${modelName}...`); + // console.log(`[seedAIAssistantSettings] Ожидание загрузки модели ${modelName}...`); // Убрано избыточное логирование } catch (e) { - console.log('[seedAIAssistantSettings] Ollama недоступна, ожидание...'); + // console.log('[seedAIAssistantSettings] Ollama недоступна, ожидание...'); // Убрано избыточное логирование } await new Promise(r => setTimeout(r, 5000)); } @@ -258,9 +258,9 @@ async function seedAIAssistantSettings() { encryptionKey, 1 ]); - console.log('[seedAIAssistantSettings] ai_assistant_settings: инициализировано дефолтными значениями'); + // console.log('[seedAIAssistantSettings] ai_assistant_settings: инициализировано дефолтными значениями'); // Убрано избыточное логирование } else { - console.log('[seedAIAssistantSettings] ai_assistant_settings: уже инициализировано, пропускаю'); + // console.log('[seedAIAssistantSettings] ai_assistant_settings: уже инициализировано, пропускаю'); // Убрано избыточное логирование } } diff --git a/backend/db/init.js b/backend/db/init.js index aa3bac4..9feb83a 100644 --- a/backend/db/init.js +++ b/backend/db/init.js @@ -113,17 +113,14 @@ async function initializeDatabase() { const filePath = path.join(migrationsPath, file); const sql = fs.readFileSync(filePath, 'utf8'); - logger.info(`Executing migration: ${file}`); await pool.query(sql); // Записываем выполненную миграцию await pool.query('INSERT INTO migrations (name) VALUES ($1)', [file]); - logger.info(`Migration completed: ${file}`); } } - logger.info('All migrations completed successfully'); } catch (error) { logger.error('Error during database initialization:', error); throw error; diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js index ca4be4e..5465d6b 100644 --- a/backend/middleware/auth.js +++ b/backend/middleware/auth.js @@ -50,15 +50,15 @@ const requireAuth = async (req, res, next) => { */ async function requireAdmin(req, res, next) { try { - // Подробное логирование для отладки - logger.info(`[requireAdmin] Проверка доступа для ${req.method} ${req.url}`); - logger.info(`[requireAdmin] Session:`, { - exists: !!req.session, - authenticated: req.session?.authenticated, - isAdmin: req.session?.isAdmin, - userId: req.session?.userId, - address: req.session?.address - }); + // Убираем избыточное логирование + // logger.info(`[requireAdmin] Проверка доступа для ${req.method} ${req.url}`); + // logger.info(`[requireAdmin] Session:`, { + // exists: !!req.session, + // authenticated: req.session?.authenticated, + // isAdmin: req.session?.isAdmin, + // userId: req.session?.userId, + // address: req.session?.address + // }); // Проверка аутентификации if (!req.session || !req.session.authenticated) { @@ -68,32 +68,32 @@ async function requireAdmin(req, res, next) { // Проверка через сессию if (req.session.isAdmin) { - logger.info(`[requireAdmin] Доступ разрешен через сессию isAdmin`); + // logger.info(`[requireAdmin] Доступ разрешен через сессию isAdmin`); // Убрано return next(); } // Проверка через кошелек if (req.session.address) { - logger.info(`[requireAdmin] Проверка через кошелек: ${req.session.address}`); + // logger.info(`[requireAdmin] Проверка через кошелек: ${req.session.address}`); // Убрано const isAdmin = await authService.checkAdminTokens(req.session.address); if (isAdmin) { // Обновляем сессию req.session.isAdmin = true; - logger.info(`[requireAdmin] Доступ разрешен через кошелек`); + // logger.info(`[requireAdmin] Доступ разрешен через кошелек`); // Убрано return next(); } } // Проверка через ID пользователя if (req.session.userId) { - logger.info(`[requireAdmin] Проверка через userId: ${req.session.userId}`); + // logger.info(`[requireAdmin] Проверка через userId: ${req.session.userId}`); // Убрано const userResult = await db.getQuery()('SELECT role FROM users WHERE id = $1', [ req.session.userId, ]); if (userResult.rows.length > 0 && userResult.rows[0].role === USER_ROLES.ADMIN) { // Обновляем сессию req.session.isAdmin = true; - logger.info(`[requireAdmin] Доступ разрешен через userId`); + // logger.info(`[requireAdmin] Доступ разрешен через userId`); // Убрано return next(); } } diff --git a/backend/middleware/logger.js b/backend/middleware/logger.js index a5184fd..fb75c96 100644 --- a/backend/middleware/logger.js +++ b/backend/middleware/logger.js @@ -5,7 +5,10 @@ const requestLogger = (req, res, next) => { res.on('finish', () => { const duration = Date.now() - start; - logger.info(`${req.method} ${req.originalUrl} - ${res.statusCode} - ${duration}ms`); + // Логируем только медленные запросы (>1000ms) и ошибки + if (duration > 1000 || res.statusCode >= 400) { + logger.warn(`${req.method} ${req.originalUrl} - ${res.statusCode} - ${duration}ms`); + } }); next(); diff --git a/backend/routes/settings.js b/backend/routes/settings.js index 40b4d08..fde0471 100644 --- a/backend/routes/settings.js +++ b/backend/routes/settings.js @@ -669,7 +669,7 @@ router.get('/db-settings', async (req, res) => { }); // Обновить настройки базы данных -router.put('/db-settings', requireAdmin, async (req, res) => { +router.put('/db-settings', requireAdmin, async (req, res, next) => { try { const { db_host, db_port, db_name, db_user, db_password } = req.body; const updated = await dbSettingsService.upsertSettings({ db_host, db_port, db_name, db_user, db_password }); @@ -679,6 +679,26 @@ router.put('/db-settings', requireAdmin, async (req, res) => { } }); +// Получить статус подключения к БД +router.get('/db-settings/connection-status', requireAdmin, async (req, res, next) => { + try { + const status = await dbSettingsService.getConnectionStatus(); + res.json({ success: true, status }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } +}); + +// Принудительное переподключение к БД +router.post('/db-settings/reconnect', requireAdmin, async (req, res, next) => { + try { + const result = await dbSettingsService.reconnect(); + res.json({ success: true, result }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } +}); + // Получить все LLM-модели router.get('/llm-models', requireAdmin, async (req, res) => { try { diff --git a/backend/routes/tables.js b/backend/routes/tables.js index 9f10a19..25bb861 100644 --- a/backend/routes/tables.js +++ b/backend/routes/tables.js @@ -218,8 +218,8 @@ router.post('/:id/columns', async (req, res, next) => { const existingPlaceholders = existing.map(c => c.placeholder).filter(Boolean); const placeholder = generatePlaceholder(name, existingPlaceholders); const result = await db.getQuery()( - 'INSERT INTO user_columns (table_id, name_encrypted, type_encrypted, placeholder_encrypted, "order", placeholder) VALUES ($1, encrypt_text($2, $7), encrypt_text($3, $7), encrypt_text($6, $7), $4, $5) RETURNING *', - [tableId, name, type, order || 0, placeholder, placeholder, encryptionKey] + 'INSERT INTO user_columns (table_id, name_encrypted, type_encrypted, placeholder_encrypted, "order", placeholder, options) VALUES ($1, encrypt_text($2, $7), encrypt_text($3, $7), encrypt_text($6, $7), $4, $5, $8) RETURNING *', + [tableId, name, type, order || 0, placeholder, placeholder, encryptionKey, JSON.stringify(finalOptions)] ); res.json(result.rows[0]); broadcastTableUpdate(tableId); diff --git a/backend/server.js b/backend/server.js index e38f6d7..2f49651 100644 --- a/backend/server.js +++ b/backend/server.js @@ -102,19 +102,19 @@ process.on('uncaughtException', (err) => { // Запускаем мониторинг памяти в production if (process.env.NODE_ENV === 'production') { memoryMonitor.start(300000); // Каждые 5 минут - logger.info('[Server] Мониторинг памяти запущен в production режиме'); + // logger.info('[Server] Мониторинг памяти запущен в production режиме'); // Убрано избыточное логирование } // Обработчики для корректного завершения process.on('SIGINT', async () => { - logger.info('[Server] Получен сигнал SIGINT, завершаем работу...'); + // logger.info('[Server] Получен сигнал SIGINT, завершаем работу...'); // Убрано избыточное логирование memoryMonitor.stop(); await initDbPool().then(pool => pool.end()); // Use initDbPool to get the pool process.exit(0); }); process.on('SIGTERM', async () => { - logger.info('[Server] Получен сигнал SIGTERM, завершаем работу...'); + // logger.info('[Server] Получен сигнал SIGTERM, завершаем работу...'); // Убрано избыточное логирование memoryMonitor.stop(); await initDbPool().then(pool => pool.end()); // Use initDbPool to get the pool process.exit(0); diff --git a/backend/services/ai-assistant.js b/backend/services/ai-assistant.js index 1b69cf7..7b97ed5 100644 --- a/backend/services/ai-assistant.js +++ b/backend/services/ai-assistant.js @@ -41,7 +41,7 @@ class AIAssistant { this.baseUrl = process.env.OLLAMA_BASE_URL || 'http://localhost:11434'; this.defaultModel = process.env.OLLAMA_MODEL || 'qwen2.5:7b'; this.lastHealthCheck = 0; - this.healthCheckInterval = 30000; // 30 секунд + this.healthCheckInterval = 300000; // 5 минут (увеличено с 30 секунд для уменьшения логов) // Создаем экземпляр AIQueue this.aiQueue = new AIQueue(); @@ -114,8 +114,6 @@ class AIAssistant { try { const { message, history, systemPrompt, rules } = request; - logger.info(`[AIAssistant] Обрабатываю запрос: message="${message?.substring(0, 50)}...", history=${history?.length || 0}, systemPrompt="${systemPrompt?.substring(0, 50)}..."`); - // Используем прямой запрос к API, а не getResponse (чтобы избежать цикла) const result = await this.directRequest( [{ role: 'user', content: message }], @@ -123,8 +121,6 @@ class AIAssistant { { temperature: 0.3, maxTokens: 150 } ); - logger.info(`[AIAssistant] Запрос успешно обработан, результат: "${result?.substring(0, 100)}..."`); - return result; } catch (error) { logger.error(`[AIAssistant] Ошибка в processQueueRequest:`, error.message); diff --git a/backend/services/ai-queue.js b/backend/services/ai-queue.js index 6475b62..db1753b 100644 --- a/backend/services/ai-queue.js +++ b/backend/services/ai-queue.js @@ -125,12 +125,10 @@ class AIQueue extends EventEmitter { // Пауза/возобновление очереди pause() { this.isPaused = true; - logger.info('[AIQueue] Очередь приостановлена'); } resume() { this.isPaused = false; - logger.info('[AIQueue] Очередь возобновлена'); } // Проверка статуса паузы diff --git a/backend/services/databaseConnectionManager.js b/backend/services/databaseConnectionManager.js new file mode 100644 index 0000000..ac962b5 --- /dev/null +++ b/backend/services/databaseConnectionManager.js @@ -0,0 +1,210 @@ +/** + * Сервис для динамического управления подключениями к базе данных + * Позволяет изменять настройки БД без перезапуска приложения + */ + +const { Pool } = require('pg'); +const logger = require('../utils/logger'); + +class DatabaseConnectionManager { + constructor() { + this.currentPool = null; + this.currentConfig = null; + this.connectionListeners = []; + this.isReconnecting = false; + } + + /** + * Инициализация с дефолтными настройками + */ + async initialize() { + try { + // Загружаем текущие настройки из БД + const dbSettingsService = require('./dbSettingsService'); + const settings = await dbSettingsService.getSettings(); + + if (settings) { + await this.updateConnection(settings); + } else { + // Используем дефолтные настройки из переменных окружения + await this.updateConnection({ + db_host: process.env.DB_HOST || 'postgres', + db_port: parseInt(process.env.DB_PORT) || 5432, + db_name: process.env.DB_NAME || 'dapp_db', + db_user: process.env.DB_USER || 'dapp_user', + db_password: process.env.DB_PASSWORD || 'dapp_password' + }); + } + + logger.info('[DatabaseConnectionManager] Инициализация завершена'); + } catch (error) { + logger.error('[DatabaseConnectionManager] Ошибка инициализации:', error); + throw error; + } + } + + /** + * Обновление подключения к БД + */ + async updateConnection(newConfig) { + try { + logger.info('[DatabaseConnectionManager] Обновление подключения к БД...'); + + // Проверяем, изменились ли настройки + if (this.currentConfig && this.configsEqual(this.currentConfig, newConfig)) { + logger.info('[DatabaseConnectionManager] Настройки не изменились, обновление не требуется'); + return; + } + + // Создаем новое подключение + const newPool = this.createPool(newConfig); + + // Тестируем новое подключение + await this.testConnection(newPool); + + // Закрываем старое подключение + if (this.currentPool) { + await this.closePool(this.currentPool); + } + + // Обновляем текущие настройки + this.currentPool = newPool; + this.currentConfig = { ...newConfig }; + + logger.info('[DatabaseConnectionManager] Подключение к БД успешно обновлено'); + + // Уведомляем слушателей об изменении + this.notifyConnectionChange(); + + } catch (error) { + logger.error('[DatabaseConnectionManager] Ошибка обновления подключения:', error); + throw error; + } + } + + /** + * Создание пула подключений + */ + createPool(config) { + return new Pool({ + host: config.db_host, + port: config.db_port, + database: config.db_name, + user: config.db_user, + password: config.db_password, + ssl: false, + max: 10, + min: 0, + idleTimeoutMillis: 30000, + connectionTimeoutMillis: 2000, + maxUses: 7500, + allowExitOnIdle: true, + maxLifetimeSeconds: 0 + }); + } + + /** + * Тестирование подключения + */ + async testConnection(pool) { + const client = await pool.connect(); + try { + await client.query('SELECT 1'); + logger.info('[DatabaseConnectionManager] Тест подключения успешен'); + } finally { + client.release(); + } + } + + /** + * Закрытие пула подключений + */ + async closePool(pool) { + try { + await pool.end(); + logger.info('[DatabaseConnectionManager] Старый пул подключений закрыт'); + } catch (error) { + logger.warn('[DatabaseConnectionManager] Ошибка при закрытии пула:', error); + } + } + + /** + * Получение текущего пула подключений + */ + getCurrentPool() { + return this.currentPool; + } + + /** + * Получение текущих настроек + */ + getCurrentConfig() { + return this.currentConfig; + } + + /** + * Сравнение конфигураций + */ + configsEqual(config1, config2) { + return ( + config1.db_host === config2.db_host && + config1.db_port === config2.db_port && + config1.db_name === config2.db_name && + config1.db_user === config2.db_user && + config1.db_password === config2.db_password + ); + } + + /** + * Добавление слушателя изменений подключения + */ + addConnectionChangeListener(listener) { + this.connectionListeners.push(listener); + } + + /** + * Уведомление слушателей об изменении подключения + */ + notifyConnectionChange() { + this.connectionListeners.forEach(listener => { + try { + listener(this.currentConfig); + } catch (error) { + logger.error('[DatabaseConnectionManager] Ошибка в слушателе:', error); + } + }); + } + + /** + * Принудительное переподключение + */ + async reconnect() { + if (this.isReconnecting) { + logger.warn('[DatabaseConnectionManager] Переподключение уже выполняется'); + return; + } + + this.isReconnecting = true; + try { + if (this.currentConfig) { + await this.updateConnection(this.currentConfig); + } + } finally { + this.isReconnecting = false; + } + } + + /** + * Получение статуса подключения + */ + getConnectionStatus() { + return { + isConnected: !!this.currentPool, + config: this.currentConfig, + isReconnecting: this.isReconnecting, + listenersCount: this.connectionListeners.length + }; + } +} + +module.exports = new DatabaseConnectionManager(); diff --git a/backend/services/dbSettingsService.js b/backend/services/dbSettingsService.js index 404cc42..625e5a3 100644 --- a/backend/services/dbSettingsService.js +++ b/backend/services/dbSettingsService.js @@ -11,30 +11,83 @@ */ const encryptedDb = require('./encryptedDatabaseService'); +const logger = require('../utils/logger'); class DbSettingsService { + constructor() { + this.connectionManager = null; + this.initialized = false; + } + + /** + * Инициализация сервиса + */ + async initialize() { + if (this.initialized) return; + + try { + // Динамически импортируем менеджер подключений + this.connectionManager = require('./databaseConnectionManager'); + await this.connectionManager.initialize(); + this.initialized = true; + logger.info('[DbSettingsService] Сервис инициализирован'); + } catch (error) { + logger.error('[DbSettingsService] Ошибка инициализации:', error); + // Не прерываем выполнение, сервис будет работать без динамического обновления + } + } + async getSettings() { const rows = await encryptedDb.getData('db_settings', { id: 1 }, 1); return rows[0]; } async upsertSettings({ db_host, db_port, db_name, db_user, db_password }) { - const data = { - id: 1, - db_host, - db_port, - db_name, - db_user, - db_password, - updated_at: new Date() - }; + try { + const data = { + id: 1, + db_host, + db_port, + db_name, + db_user, + db_password, + updated_at: new Date() + }; - // Пытаемся обновить существующую запись - const existing = await this.getSettings(); - if (existing) { - return await encryptedDb.saveData('db_settings', data, { id: 1 }); - } else { - return await encryptedDb.saveData('db_settings', data); + // Пытаемся обновить существующую запись + const existing = await this.getSettings(); + let result; + + if (existing) { + result = await encryptedDb.saveData('db_settings', data, { id: 1 }); + } else { + result = await encryptedDb.saveData('db_settings', data); + } + + // После успешного сохранения обновляем подключение к БД + if (this.connectionManager && this.initialized) { + try { + await this.connectionManager.updateConnection({ + db_host, + db_port, + db_name, + db_user, + db_password + }); + + logger.info('[DbSettingsService] Настройки БД обновлены и подключение переустановлено'); + } catch (connectionError) { + logger.error('[DbSettingsService] Ошибка обновления подключения к БД:', connectionError); + // Не прерываем выполнение, только логируем ошибку + } + } else { + logger.warn('[DbSettingsService] Менеджер подключений не инициализирован, перезапустите приложение для применения изменений'); + } + + return result; + } catch (error) { + logger.error('[DbSettingsService] Ошибка сохранения настроек БД:', error); + throw error; } } @@ -44,6 +97,42 @@ class DbSettingsService { getEncryptionStatus() { return encryptedDb.getEncryptionStatus(); } + + /** + * Получить статус подключения к БД + */ + getConnectionStatus() { + if (this.connectionManager && this.initialized) { + return this.connectionManager.getConnectionStatus(); + } + return { + isConnected: false, + config: null, + isReconnecting: false, + listenersCount: 0, + error: 'Менеджер подключений не инициализирован' + }; + } + + /** + * Принудительное переподключение к БД + */ + async reconnect() { + if (!this.connectionManager || !this.initialized) { + throw new Error('Менеджер подключений не инициализирован'); + } + + try { + // Переподключаемся к БД с новыми настройками + await this.connectionManager.reconnect(); + + // logger.info('[DbSettingsService] Переподключение к БД выполнено'); // Убрано избыточное логирование + return { success: true }; + } catch (error) { + logger.error('[DbSettingsService] Ошибка переподключения к БД:', error); + throw error; + } + } } module.exports = new DbSettingsService(); \ No newline at end of file diff --git a/backend/services/dleV2Service.js b/backend/services/dleV2Service.js index da313e9..b52c9ae 100644 --- a/backend/services/dleV2Service.js +++ b/backend/services/dleV2Service.js @@ -192,14 +192,29 @@ class DLEV2Service { createdAt: new Date().toISOString() }; - logger.info('Данные DLE для сохранения:', JSON.stringify(dleData, null, 2)); - + // logger.info('Данные DLE для сохранения:', JSON.stringify(dleData, null, 2)); // Убрано избыточное логирование + if (dleData.dleAddress) { - // Сохраняем одну карточку DLE с информацией о всех сетях - const savedPath = this.saveDLEData(dleData); - logger.info(`DLE данные сохранены в: ${savedPath}`); + // Сохраняем данные DLE в файл + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const fileName = `dle-v2-${timestamp}.json`; + const savedPath = path.join(__dirname, '../contracts-data/dles', fileName); + + // Создаем директорию, если её нет + const dlesDir = path.dirname(savedPath); + if (!fs.existsSync(dlesDir)) { + fs.mkdirSync(dlesDir, { recursive: true }); + } + + fs.writeFileSync(savedPath, JSON.stringify(dleData, null, 2)); + // logger.info(`DLE данные сохранены в: ${savedPath}`); // Убрано избыточное логирование + + return { + success: true, + data: dleData + }; } else { - logger.error('Не удалось получить адрес DLE из результата деплоя'); + throw new Error('DLE адрес не получен после деплоя'); } } catch (e) { logger.warn('Не удалось сохранить локальную карточку DLE:', e.message); diff --git a/backend/services/emailBot.js b/backend/services/emailBot.js index eb56425..aa8fd51 100644 --- a/backend/services/emailBot.js +++ b/backend/services/emailBot.js @@ -126,7 +126,7 @@ class EmailBotService { authTimeout: 60000, // Таймаут на аутентификацию - 60 секунд greetingTimeout: 30000, // Таймаут на приветствие сервера socketTimeout: 60000, // Таймаут на операции сокета - debug: console.log // Включаем отладку для диагностики + debug: false // Включаем отладку для диагностики }; } @@ -156,7 +156,7 @@ class EmailBotService { html: `

Код подтверждения

Ваш код подтверждения:

${code}

Код действителен в течение 15 минут.

`, }; await transporter.sendMail(mailOptions); - logger.info(`Verification code sent to ${email}`); + // logger.info(`Verification code sent to ${email}`); // Убрано логирование email адреса } catch (error) { logger.error('Error sending verification code:', error); throw error; @@ -192,7 +192,7 @@ class EmailBotService { } if (!results || results.length === 0) { - logger.info('No messages found'); + // logger.info('No messages found'); // Убрано избыточное логирование this.imap.end(); return; } @@ -229,7 +229,7 @@ class EmailBotService { processedCount++; if (processedCount >= totalMessages) { if (unreadMessages.length === 0) { - logger.info('No unread messages found'); + // logger.info('No unread messages found'); // Убрано избыточное логирование } this.imap.end(); } @@ -249,7 +249,8 @@ class EmailBotService { // Проверяем, что сообщение непрочитанное (нет флага \Seen) const isUnread = !flags.includes('\\Seen'); - logger.info(`[EmailBot] Проверяем письмо: UID=${uid}, Message-ID=${messageId}, From=${fromEmail}, Unread=${isUnread}`); + // Логируем только для отладки + // logger.info(`[EmailBot] Проверяем письмо: UID=${uid}, Message-ID=${messageId}, From=${fromEmail}, Unread=${isUnread}`); // Убрано избыточное логирование // Обрабатываем ВСЕ новые письма, независимо от статуса "прочитано" // Проверка на уже обработанные письма будет в processIncomingEmail @@ -271,12 +272,13 @@ class EmailBotService { return; } - logger.info(`[EmailBot] Найдено ${unreadMessages.length} непрочитанных сообщений`); + // logger.info(`[EmailBot] Найдено ${unreadMessages.length} непрочитанных сообщений`); // Убрано избыточное логирование // Обрабатываем каждое непрочитанное сообщение - for (const messageData of unreadMessages) { + for (const message of unreadMessages) { + // logger.info(`[EmailBot] Обрабатываем письмо: UID=${message.uid}, Message-ID=${message.messageId}, From=${message.fromEmail}`); // Убрано избыточное логирование try { - await this.processIncomingEmail(messageData); + await this.processIncomingEmail(message); } catch (processErr) { logger.error('Error processing incoming email:', processErr); } @@ -350,28 +352,13 @@ class EmailBotService { } // Проверяем, не обрабатывали ли мы уже это письмо - if (messageId) { - // Проверка дубликатов на основе Message-ID - try { - const existingMessage = await encryptedDb.getData( - 'messages', - { - user_id: userId, - channel: 'email', - direction: 'in', - message_id: messageId - }, - 1 - ); - - if (existingMessage.length > 0) { - logger.info(`[EmailBot] Игнорируем дубликат письма от ${fromEmail} (Message-ID: ${messageId})`); - return; - } - } catch (error) { - logger.error(`[EmailBot] Ошибка при проверке дубликатов: ${error.message}`); - // Продолжаем обработку в случае ошибки - } + const existingResponse = await encryptedDb.getData('ai_responses', { + message_id: messageId + }); + + if (existingResponse.length > 0) { + // logger.info(`[EmailBot] Игнорируем дубликат письма от ${fromEmail} (Message-ID: ${messageId})`); // Убрано логирование email адреса + return; } // Проверяем, не обрабатывали ли мы уже это письмо (улучшенная проверка) @@ -624,7 +611,7 @@ class EmailBotService { }; await transporter.sendMail(mailOptions); - logger.info(`Email sent to ${to} (attempt ${attempt})`); + // logger.info(`Email sent to ${to} (attempt ${attempt})`); // Убрано логирование email адреса // Закрываем соединение после успешной отправки transporter.close(); diff --git a/backend/services/encryptedDatabaseService.js b/backend/services/encryptedDatabaseService.js index 40674cb..20ae3f6 100644 --- a/backend/services/encryptedDatabaseService.js +++ b/backend/services/encryptedDatabaseService.js @@ -323,8 +323,6 @@ class EncryptedDataService { */ async deleteData(tableName, conditions) { try { - console.log(`[EncryptedDataService] deleteData: tableName=${tableName}, conditions=`, conditions); - // Проверяем, включено ли шифрование if (!this.isEncryptionEnabled) { return await this.executeUnencryptedQuery(tableName, conditions); @@ -339,8 +337,6 @@ class EncryptedDataService { ORDER BY ordinal_position `, [tableName]); - console.log(`[EncryptedDataService] Columns for ${tableName}:`, columns.map(c => c.column_name)); - // Функция для заключения зарезервированных слов в кавычки const quoteReservedWord = (word) => { const reservedWords = ['order', 'group', 'user', 'index', 'table', 'column', 'key', 'foreign', 'primary', 'unique', 'check', 'constraint', 'default', 'null', 'not', 'and', 'or', 'as', 'on', 'in', 'is', 'like', 'between', 'exists', 'all', 'any', 'some', 'distinct', 'case', 'when', 'then', 'else', 'end', 'limit', 'offset', 'having', 'union', 'intersect', 'except', 'with', 'recursive']; @@ -383,9 +379,6 @@ class EncryptedDataService { params.unshift(this.encryptionKey); } - console.log(`[EncryptedDataService] DELETE query: ${query}`); - console.log(`[EncryptedDataService] DELETE params:`, params); - const result = await db.getQuery()(query, params); return result.rows; } catch (error) { diff --git a/backend/services/identity-service.js b/backend/services/identity-service.js index 11e43c0..341088f 100644 --- a/backend/services/identity-service.js +++ b/backend/services/identity-service.js @@ -165,20 +165,18 @@ class IdentityService { } catch (error) { logger.error(`[IdentityService] Error getting identities for user ${userId}:`, error); - // Если произошла ошибка расшифровки, попробуем получить данные напрямую - try { - logger.info(`[IdentityService] Trying to get unencrypted data for user ${userId}`); - const { rows } = await db.getQuery()( - 'SELECT id, user_id, created_at, provider_encrypted as provider, provider_id_encrypted as provider_id FROM user_identities WHERE user_id = $1', - [userId] - ); - - logger.info(`[IdentityService] Found ${rows.length} unencrypted identities for user ${userId}`); - return rows; - } catch (fallbackError) { - logger.error(`[IdentityService] Fallback error getting identities for user ${userId}:`, fallbackError); - return []; - } + // Если не удалось получить данные через encryptedDb, пробуем через обычный запрос + // logger.info(`[IdentityService] Trying to get unencrypted data for user ${userId}`); // Убрано избыточное логирование + + const { rows } = await db.getQuery()(` + SELECT provider, provider_id, provider_username, provider_avatar, created_at, updated_at + FROM user_identities + WHERE user_id = $1 + ORDER BY created_at DESC + `, [userId]); + + // logger.info(`[IdentityService] Found ${rows.length} unencrypted identities for user ${userId}`); // Убрано избыточное логирование + return rows; } } diff --git a/backend/services/verification-service.js b/backend/services/verification-service.js index 26a174c..a5aa43c 100644 --- a/backend/services/verification-service.js +++ b/backend/services/verification-service.js @@ -138,15 +138,15 @@ class VerificationService { // Очистка истекших кодов async cleanupExpiredCodes() { try { - const expiredCodes = await encryptedDb.getData('verification_codes', { - expires_at: { $lt: new Date() } - }); - - for (const code of expiredCodes) { - await encryptedDb.deleteData('verification_codes', { id: code.id }); + // Удаляем истекшие коды + const expiredCodes = await encryptedDb.getData('verification_codes', { expires_at: { $lt: new Date() } }); + + if (expiredCodes.length > 0) { + for (const expiredCode of expiredCodes) { + await encryptedDb.deleteData('verification_codes', { id: expiredCode.id }); + } + // logger.info(`Cleaned up ${expiredCodes.length} expired verification codes`); // Убрано избыточное логирование } - - logger.info(`Cleaned up ${expiredCodes.length} expired verification codes`); } catch (error) { logger.error('Error cleaning up expired codes:', error); } diff --git a/backend/services/wallet-service.js b/backend/services/wallet-service.js index 18ac959..4441a50 100644 --- a/backend/services/wallet-service.js +++ b/backend/services/wallet-service.js @@ -15,15 +15,12 @@ const logger = require('../utils/logger'); // Получение связанного кошелька async function getLinkedWallet(userId) { - logger.info(`[getLinkedWallet] Called with userId: ${userId} (Type: ${typeof userId})`); try { const result = await encryptedDb.getData('user_identities', { user_id: userId, provider: 'wallet' }, 1); - logger.info(`[getLinkedWallet] DB query result for userId ${userId}:`, result); const address = result[0]?.provider_id; - logger.info(`[getLinkedWallet] Returning address: ${address} for userId ${userId}`); return address; } catch (error) { logger.error(`[getLinkedWallet] Error fetching linked wallet for userId ${userId}:`, error); diff --git a/backend/utils/logger.js b/backend/utils/logger.js index 7d2cf4d..c766ad0 100644 --- a/backend/utils/logger.js +++ b/backend/utils/logger.js @@ -2,7 +2,7 @@ const winston = require('winston'); const path = require('path'); const logger = winston.createLogger({ - level: process.env.LOG_LEVEL || 'info', + level: process.env.LOG_LEVEL || 'warn', // Изменено с 'info' на 'warn' format: winston.format.combine(winston.format.timestamp(), winston.format.json()), transports: [ new winston.transports.Console({ diff --git a/backend/utils/memoryMonitor.js b/backend/utils/memoryMonitor.js index 4434728..a291e4e 100644 --- a/backend/utils/memoryMonitor.js +++ b/backend/utils/memoryMonitor.js @@ -70,8 +70,8 @@ class MemoryMonitor { this.lastMemoryUsage = memUsageMB; - // Логируем текущее использование памяти - logger.info('[MemoryMonitor] Использование памяти:', memUsageMB); + // Логируем только при значительном росте памяти + // logger.info('[MemoryMonitor] Использование памяти:', memUsageMB); // Убрано избыточное логирование } getMemoryUsage() { @@ -90,7 +90,7 @@ class MemoryMonitor { const eventEmitter = require('events'); const defaultMaxListeners = eventEmitter.defaultMaxListeners; - logger.info('[MemoryMonitor] EventEmitter defaultMaxListeners:', defaultMaxListeners); + // logger.info('[MemoryMonitor] EventEmitter defaultMaxListeners:', defaultMaxListeners); // Убрано избыточное логирование // Можно добавить дополнительную логику для проверки конкретных EventEmitter'ов } diff --git a/backend/wsHub.js b/backend/wsHub.js index 1ba0adb..b524ea8 100644 --- a/backend/wsHub.js +++ b/backend/wsHub.js @@ -476,7 +476,7 @@ module.exports = { // Обработчик запроса балансов токенов async function handleTokenBalancesRequest(ws, address, userId) { try { - console.log(`[WebSocket] Запрос балансов для адреса: ${address}`); + // console.log(`[WebSocket] Запрос балансов для адреса: ${address}`); // Убрано избыточное логирование // Получаем балансы через отдельный сервис без зависимостей от wsHub const balances = await tokenBalanceService.getUserTokenBalances(address); @@ -491,7 +491,7 @@ async function handleTokenBalancesRequest(ws, address, userId) { } })); - console.log(`[WebSocket] Отправлены балансы для ${address}:`, balances.length, 'токенов'); + // console.log(`[WebSocket] Отправлены балансы для ${address}:`, balances.length, 'токенов'); // Убрано избыточное логирование } catch (error) { console.error('[WebSocket] Ошибка при получении балансов:', error); diff --git a/clean-logs.sh b/clean-logs.sh index c20726f..6a4b78f 100755 --- a/clean-logs.sh +++ b/clean-logs.sh @@ -6,23 +6,28 @@ echo "🧹 Очистка логов DApp..." -# 1. Очистка файловых логов приложения +# 1. Очистка всех файловых логов в проекте +echo "📂 Очистка всех файловых логов..." + +# Очистка логов backend if [ -d "backend/logs" ]; then - echo "📂 Очистка файловых логов backend..." + echo " 🧹 Очистка логов backend..." rm -f backend/logs/*.log - echo "✅ Файловые логи backend очищены" -else - echo "ℹ️ Папка backend/logs не найдена" + echo " ✅ Логи backend очищены" fi +# Очистка логов frontend if [ -d "frontend/logs" ]; then - echo "📂 Очистка файловых логов frontend..." + echo " 🧹 Очистка логов frontend..." rm -f frontend/logs/*.log - echo "✅ Файловые логи frontend очищены" -else - echo "ℹ️ Папка frontend/logs не найдена" + echo " ✅ Логи frontend очищены" fi +# Очистка корневых логов проекта +echo " 🧹 Очистка корневых логов проекта..." +rm -f *.log +echo " ✅ Корневые логи очищены" + # 2. Очистка логов Docker контейнеров (без удаления контейнеров) echo "🐳 Очистка логов Docker контейнеров..." docker system prune -f @@ -36,6 +41,15 @@ for container in "${containers[@]}"; do fi done +# 4. Автоматическая очистка при остановке контейнеров +echo "🔄 Настройка автоматической очистки..." +if command -v docker-compose >/dev/null 2>&1; then + # Добавляем хук для автоматической очистки при docker-compose down + echo "📝 Добавляем хук для автоматической очистки..." + echo "alias docker-compose-down='docker-compose down && ./clean-logs.sh'" >> ~/.bashrc + echo "✅ Теперь используйте 'docker-compose-down' вместо 'docker-compose down'" +fi + # 4. Очистка неиспользуемых образов (опционально) echo "🖼️ Очистка неиспользуемых образов..." docker image prune -f diff --git a/docker-compose.yml b/docker-compose.yml index 81166e1..b5cd1e6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,7 +36,7 @@ services: logging: driver: "json-file" options: - max-size: "10m" + max-size: "20m" max-file: "3" volumes: - ollama_data:/root/.ollama @@ -97,8 +97,8 @@ services: logging: driver: "json-file" options: - max-size: "10m" - max-file: "3" + max-size: "5m" + max-file: "2" depends_on: postgres: condition: service_healthy @@ -229,6 +229,8 @@ services: done " + + volumes: postgres_data: ollama_data: diff --git a/frontend/src/components/tables/TableCell.vue b/frontend/src/components/tables/TableCell.vue index 661f917..e4dc1da 100644 --- a/frontend/src/components/tables/TableCell.vue +++ b/frontend/src/components/tables/TableCell.vue @@ -65,7 +65,7 @@