ваше сообщение коммита
This commit is contained in:
@@ -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 };
|
||||
|
||||
@@ -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: уже инициализировано, пропускаю'); // Убрано избыточное логирование
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -125,12 +125,10 @@ class AIQueue extends EventEmitter {
|
||||
// Пауза/возобновление очереди
|
||||
pause() {
|
||||
this.isPaused = true;
|
||||
logger.info('[AIQueue] Очередь приостановлена');
|
||||
}
|
||||
|
||||
resume() {
|
||||
this.isPaused = false;
|
||||
logger.info('[AIQueue] Очередь возобновлена');
|
||||
}
|
||||
|
||||
// Проверка статуса паузы
|
||||
|
||||
210
backend/services/databaseConnectionManager.js
Normal file
210
backend/services/databaseConnectionManager.js
Normal file
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
|
||||
@@ -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: `<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;"><h2 style="color: #333;">Код подтверждения</h2><p style="font-size: 16px; color: #666;">Ваш код подтверждения:</p><div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; text-align: center; margin: 20px 0;"><span style="font-size: 24px; font-weight: bold; color: #333;">${code}</span></div><p style="font-size: 14px; color: #999;">Код действителен в течение 15 минут.</p></div>`,
|
||||
};
|
||||
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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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'ов
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user