feat: новая функция
This commit is contained in:
@@ -20,7 +20,7 @@ const rateLimit = require('express-rate-limit');
|
||||
const { requireAuth } = require('../middleware/auth');
|
||||
const authService = require('../services/auth-service');
|
||||
const { ethers } = require('ethers');
|
||||
const { initTelegramAuth } = require('../services/telegramBot');
|
||||
const botManager = require('../services/botManager');
|
||||
const emailAuth = require('../services/emailAuth');
|
||||
const verificationService = require('../services/verification-service');
|
||||
const identityService = require('../services/identity-service');
|
||||
@@ -60,17 +60,10 @@ router.get('/nonce', async (req, res) => {
|
||||
// Используем правильный ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = path.join(__dirname, '../ssl/keys/full_db_encryption.key');
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
logger.info(`[nonce] Using encryption key: ${encryptionKey.substring(0, 10)}...`);
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
logger.info(`[nonce] Using encryption key: ${encryptionKey.substring(0, 10)}...`);
|
||||
|
||||
try {
|
||||
// Проверяем, существует ли уже nonce для этого адреса
|
||||
@@ -135,16 +128,9 @@ router.post('/verify', async (req, res) => {
|
||||
// Читаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = path.join(__dirname, '../ssl/keys/full_db_encryption.key');
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
// Проверяем nonce в базе данных с проверкой времени истечения
|
||||
const nonceResult = await db.getQuery()(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -42,16 +42,9 @@ router.post('/link', requireAuth, async (req, res, next) => {
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = path.join(__dirname, '../ssl/keys/full_db_encryption.key');
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
// Проверяем, существует ли уже такой кошелек
|
||||
const existingCheck = await db.getQuery()(
|
||||
@@ -150,167 +143,16 @@ router.delete('/:provider/:providerId', requireAuth, async (req, res, next) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Получение email-настроек
|
||||
router.get('/email-settings', requireAuth, async (req, res, next) => {
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = path.join(__dirname, '../ssl/keys/full_db_encryption.key');
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
|
||||
try {
|
||||
const { rows } = await db.getQuery()(
|
||||
'SELECT id, smtp_port, imap_port, created_at, updated_at, decrypt_text(smtp_host_encrypted, $1) as smtp_host, decrypt_text(smtp_user_encrypted, $1) as smtp_user, decrypt_text(smtp_password_encrypted, $1) as smtp_password, decrypt_text(imap_host_encrypted, $1) as imap_host, decrypt_text(from_email_encrypted, $1) as from_email FROM email_settings ORDER BY id LIMIT 1',
|
||||
[encryptionKey]
|
||||
);
|
||||
if (!rows.length) return res.status(404).json({ success: false, error: 'Not found' });
|
||||
const settings = rows[0];
|
||||
delete settings.smtp_password; // не возвращаем пароль
|
||||
res.json({ success: true, settings });
|
||||
} catch (error) {
|
||||
logger.error('Error getting email settings:', error, error && error.stack);
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Обновление email-настроек
|
||||
router.put('/email-settings', requireAuth, async (req, res, next) => {
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = path.join(__dirname, '../ssl/keys/full_db_encryption.key');
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
|
||||
try {
|
||||
const { smtp_host, smtp_port, smtp_user, smtp_password, imap_host, imap_port, from_email } = req.body;
|
||||
if (!smtp_host || !smtp_port || !smtp_user || !from_email) {
|
||||
return res.status(400).json({ success: false, error: 'Missing required fields' });
|
||||
}
|
||||
const { rows } = await db.getQuery()('SELECT id FROM email_settings ORDER BY id LIMIT 1');
|
||||
if (rows.length) {
|
||||
// Обновляем существующую запись
|
||||
await db.getQuery()(
|
||||
`UPDATE email_settings SET smtp_host_encrypted=encrypt_text($1, $9), smtp_port=$2, smtp_user_encrypted=encrypt_text($3, $9), smtp_password_encrypted=COALESCE(encrypt_text($4, $9), smtp_password_encrypted), imap_host_encrypted=encrypt_text($5, $9), imap_port=$6, from_email_encrypted=encrypt_text($7, $9), updated_at=NOW() WHERE id=$8`,
|
||||
[smtp_host, smtp_port, smtp_user, smtp_password, imap_host, imap_port, from_email, rows[0].id, encryptionKey]
|
||||
);
|
||||
} else {
|
||||
// Вставляем новую
|
||||
await db.getQuery()(
|
||||
`INSERT INTO email_settings (smtp_host_encrypted, smtp_port, smtp_user_encrypted, smtp_password_encrypted, imap_host_encrypted, imap_port, from_email_encrypted) VALUES (encrypt_text($1, $8), $2, encrypt_text($3, $8), encrypt_text($4, $8), encrypt_text($5, $8), $6, encrypt_text($7, $8))`,
|
||||
[smtp_host, smtp_port, smtp_user, smtp_password, imap_host, imap_port, from_email, encryptionKey]
|
||||
);
|
||||
}
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
logger.error('Error updating email settings:', error);
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Получение telegram-настроек
|
||||
router.get('/telegram-settings', requireAuth, async (req, res, next) => {
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = path.join(__dirname, '../ssl/keys/full_db_encryption.key');
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
|
||||
try {
|
||||
const { rows } = await db.getQuery()(
|
||||
'SELECT id, created_at, updated_at, decrypt_text(bot_token_encrypted, $1) as bot_token, decrypt_text(bot_username_encrypted, $1) as bot_username FROM telegram_settings ORDER BY id LIMIT 1',
|
||||
[encryptionKey]
|
||||
);
|
||||
if (!rows.length) return res.status(404).json({ success: false, error: 'Not found' });
|
||||
const settings = rows[0];
|
||||
delete settings.bot_token; // не возвращаем токен
|
||||
res.json({ success: true, settings });
|
||||
} catch (error) {
|
||||
logger.error('Error getting telegram settings:', error, error && error.stack);
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Обновление telegram-настроек
|
||||
router.put('/telegram-settings', requireAuth, async (req, res, next) => {
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = path.join(__dirname, '../ssl/keys/full_db_encryption.key');
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
|
||||
try {
|
||||
const { bot_token, bot_username } = req.body;
|
||||
if (!bot_token || !bot_username) {
|
||||
return res.status(400).json({ success: false, error: 'Missing required fields' });
|
||||
}
|
||||
const { rows } = await db.getQuery()('SELECT id FROM telegram_settings ORDER BY id LIMIT 1');
|
||||
if (rows.length) {
|
||||
// Обновляем существующую запись
|
||||
await db.getQuery()(
|
||||
`UPDATE telegram_settings SET bot_token_encrypted=encrypt_text($1, $4), bot_username_encrypted=encrypt_text($2, $4), updated_at=NOW() WHERE id=$3`,
|
||||
[bot_token, bot_username, rows[0].id, encryptionKey]
|
||||
);
|
||||
} else {
|
||||
// Вставляем новую
|
||||
await db.getQuery()(
|
||||
`INSERT INTO telegram_settings (bot_token_encrypted, bot_username_encrypted) VALUES (encrypt_text($1, $3), encrypt_text($2, $3))` ,
|
||||
[bot_token, bot_username, encryptionKey]
|
||||
);
|
||||
}
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
logger.error('Error updating telegram settings:', error);
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
// Дублирующиеся маршруты email/telegram-settings удалены - используются маршруты из settings.js
|
||||
|
||||
// Получение db-настроек
|
||||
router.get('/db-settings', requireAuth, async (req, res, next) => {
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = path.join(__dirname, '../ssl/keys/full_db_encryption.key');
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
try {
|
||||
const { rows } = await db.getQuery()(
|
||||
|
||||
@@ -14,8 +14,7 @@ const express = require('express');
|
||||
const router = express.Router();
|
||||
const db = require('../db');
|
||||
const { broadcastMessagesUpdate } = require('../wsHub');
|
||||
const telegramBot = require('../services/telegramBot');
|
||||
const emailBot = new (require('../services/emailBot'))();
|
||||
const botManager = require('../services/botManager');
|
||||
const { isUserBlocked } = require('../utils/userUtils');
|
||||
|
||||
// GET /api/messages?userId=123
|
||||
@@ -23,43 +22,32 @@ router.get('/', async (req, res) => {
|
||||
const userId = req.query.userId;
|
||||
const conversationId = req.query.conversationId;
|
||||
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = path.join(__dirname, '../ssl/keys/full_db_encryption.key');
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
try {
|
||||
let result;
|
||||
if (conversationId) {
|
||||
result = await db.getQuery()(
|
||||
`SELECT id, user_id, decrypt_text(sender_type_encrypted, $2) as sender_type, decrypt_text(content_encrypted, $2) as content, decrypt_text(channel_encrypted, $2) as channel, decrypt_text(role_encrypted, $2) as role, decrypt_text(direction_encrypted, $2) as direction, created_at, decrypt_text(attachment_filename_encrypted, $2) as attachment_filename, decrypt_text(attachment_mimetype_encrypted, $2) as attachment_mimetype, attachment_size, attachment_data, message_type
|
||||
`SELECT id, user_id, decrypt_text(sender_type_encrypted, $2) as sender_type, decrypt_text(content_encrypted, $2) as content, decrypt_text(channel_encrypted, $2) as channel, decrypt_text(role_encrypted, $2) as role, decrypt_text(direction_encrypted, $2) as direction, created_at, decrypt_text(attachment_filename_encrypted, $2) as attachment_filename, decrypt_text(attachment_mimetype_encrypted, $2) as attachment_mimetype, attachment_size, attachment_data
|
||||
FROM messages
|
||||
WHERE conversation_id = $1 AND message_type = 'user_chat'
|
||||
WHERE conversation_id = $1
|
||||
ORDER BY created_at ASC`,
|
||||
[conversationId, encryptionKey]
|
||||
);
|
||||
} else if (userId) {
|
||||
result = await db.getQuery()(
|
||||
`SELECT id, user_id, decrypt_text(sender_type_encrypted, $2) as sender_type, decrypt_text(content_encrypted, $2) as content, decrypt_text(channel_encrypted, $2) as channel, decrypt_text(role_encrypted, $2) as role, decrypt_text(direction_encrypted, $2) as direction, created_at, decrypt_text(attachment_filename_encrypted, $2) as attachment_filename, decrypt_text(attachment_mimetype_encrypted, $2) as attachment_mimetype, attachment_size, attachment_data, message_type
|
||||
`SELECT id, user_id, decrypt_text(sender_type_encrypted, $2) as sender_type, decrypt_text(content_encrypted, $2) as content, decrypt_text(channel_encrypted, $2) as channel, decrypt_text(role_encrypted, $2) as role, decrypt_text(direction_encrypted, $2) as direction, created_at, decrypt_text(attachment_filename_encrypted, $2) as attachment_filename, decrypt_text(attachment_mimetype_encrypted, $2) as attachment_mimetype, attachment_size, attachment_data
|
||||
FROM messages
|
||||
WHERE user_id = $1 AND message_type = 'user_chat'
|
||||
WHERE user_id = $1
|
||||
ORDER BY created_at ASC`,
|
||||
[userId, encryptionKey]
|
||||
);
|
||||
} else {
|
||||
result = await db.getQuery()(
|
||||
`SELECT id, user_id, decrypt_text(sender_type_encrypted, $1) as sender_type, decrypt_text(content_encrypted, $1) as content, decrypt_text(channel_encrypted, $1) as channel, decrypt_text(role_encrypted, $1) as role, decrypt_text(direction_encrypted, $1) as direction, created_at, decrypt_text(attachment_filename_encrypted, $1) as attachment_filename, decrypt_text(attachment_mimetype_encrypted, $1) as attachment_mimetype, attachment_size, attachment_data, message_type
|
||||
`SELECT id, user_id, decrypt_text(sender_type_encrypted, $1) as sender_type, decrypt_text(content_encrypted, $1) as content, decrypt_text(channel_encrypted, $1) as channel, decrypt_text(role_encrypted, $1) as role, decrypt_text(direction_encrypted, $1) as direction, created_at, decrypt_text(attachment_filename_encrypted, $1) as attachment_filename, decrypt_text(attachment_mimetype_encrypted, $1) as attachment_mimetype, attachment_size, attachment_data
|
||||
FROM messages
|
||||
WHERE message_type = 'user_chat'
|
||||
ORDER BY created_at ASC`,
|
||||
[encryptionKey]
|
||||
);
|
||||
@@ -73,48 +61,10 @@ router.get('/', async (req, res) => {
|
||||
// POST /api/messages
|
||||
router.post('/', async (req, res) => {
|
||||
const { user_id, sender_type, content, channel, role, direction, attachment_filename, attachment_mimetype, attachment_size, attachment_data } = req.body;
|
||||
|
||||
// Определяем тип сообщения
|
||||
const senderId = req.user && req.user.id;
|
||||
let messageType = 'user_chat'; // по умолчанию для публичных сообщений
|
||||
|
||||
if (senderId) {
|
||||
// Проверяем, является ли отправитель админом
|
||||
const senderCheck = await db.getQuery()(
|
||||
'SELECT role FROM users WHERE id = $1',
|
||||
[senderId]
|
||||
);
|
||||
|
||||
if (senderCheck.rows.length > 0 && (senderCheck.rows[0].role === 'editor' || senderCheck.rows[0].role === 'readonly')) {
|
||||
// Если отправитель админ, проверяем получателя
|
||||
const recipientCheck = await db.getQuery()(
|
||||
'SELECT role FROM users WHERE id = $1',
|
||||
[user_id]
|
||||
);
|
||||
|
||||
// Если получатель тоже админ, то это приватное сообщение
|
||||
if (recipientCheck.rows.length > 0 && (recipientCheck.rows[0].role === 'editor' || recipientCheck.rows[0].role === 'readonly')) {
|
||||
messageType = 'admin_chat';
|
||||
} else {
|
||||
// Если получатель обычный пользователь, то это публичное сообщение
|
||||
messageType = 'user_chat';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = path.join(__dirname, '../ssl/keys/full_db_encryption.key');
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
try {
|
||||
// Проверка блокировки пользователя
|
||||
@@ -149,72 +99,29 @@ router.post('/', async (req, res) => {
|
||||
return res.status(400).json({ error: 'У пользователя не привязан кошелёк. Сообщение не отправлено.' });
|
||||
}
|
||||
}
|
||||
// 1. Проверяем, есть ли беседа для user_id
|
||||
let conversationResult = await db.getQuery()(
|
||||
'SELECT id, user_id, created_at, updated_at, decrypt_text(title_encrypted, $2) as title FROM conversations WHERE user_id = $1 ORDER BY updated_at DESC, created_at DESC LIMIT 1',
|
||||
[user_id, encryptionKey]
|
||||
);
|
||||
let conversation;
|
||||
|
||||
if (messageType === 'admin_chat') {
|
||||
// Для админских сообщений ищем приватную беседу через conversation_participants
|
||||
let conversationResult = await db.getQuery()(`
|
||||
SELECT c.id
|
||||
FROM conversations c
|
||||
INNER JOIN conversation_participants cp1 ON cp1.conversation_id = c.id AND cp1.user_id = $1
|
||||
INNER JOIN conversation_participants cp2 ON cp2.conversation_id = c.id AND cp2.user_id = $2
|
||||
WHERE c.conversation_type = 'admin_chat'
|
||||
LIMIT 1
|
||||
`, [senderId, user_id]);
|
||||
|
||||
if (conversationResult.rows.length === 0) {
|
||||
// Создаем новую приватную беседу между админами
|
||||
const title = `Приватная беседа ${senderId} - ${user_id}`;
|
||||
const newConv = await db.getQuery()(
|
||||
'INSERT INTO conversations (user_id, title_encrypted, conversation_type, created_at, updated_at) VALUES ($1, encrypt_text($2, $3), $4, NOW(), NOW()) RETURNING *',
|
||||
[user_id, title, encryptionKey, 'admin_chat']
|
||||
);
|
||||
conversation = newConv.rows[0];
|
||||
|
||||
// Добавляем участников в беседу
|
||||
await db.getQuery()(
|
||||
'INSERT INTO conversation_participants (conversation_id, user_id) VALUES ($1, $2), ($1, $3)',
|
||||
[conversation.id, senderId, user_id]
|
||||
);
|
||||
} else {
|
||||
conversation = { id: conversationResult.rows[0].id };
|
||||
}
|
||||
} else {
|
||||
// Для обычных пользовательских сообщений используем старую логику с user_id
|
||||
let conversationResult = await db.getQuery()(
|
||||
'SELECT id, user_id, created_at, updated_at, decrypt_text(title_encrypted, $2) as title FROM conversations WHERE user_id = $1 ORDER BY updated_at DESC, created_at DESC LIMIT 1',
|
||||
[user_id, encryptionKey]
|
||||
if (conversationResult.rows.length === 0) {
|
||||
// 2. Если нет — создаём новую беседу
|
||||
const title = `Чат с пользователем ${user_id}`;
|
||||
const newConv = await db.getQuery()(
|
||||
'INSERT INTO conversations (user_id, title_encrypted, created_at, updated_at) VALUES ($1, encrypt_text($2, $3), NOW(), NOW()) RETURNING *',
|
||||
[user_id, title, encryptionKey]
|
||||
);
|
||||
|
||||
if (conversationResult.rows.length === 0) {
|
||||
// Создаем новую беседу
|
||||
const title = `Чат с пользователем ${user_id}`;
|
||||
const newConv = await db.getQuery()(
|
||||
'INSERT INTO conversations (user_id, title_encrypted, conversation_type, created_at, updated_at) VALUES ($1, encrypt_text($2, $3), $4, NOW(), NOW()) RETURNING *',
|
||||
[user_id, title, encryptionKey, 'user_chat']
|
||||
);
|
||||
conversation = newConv.rows[0];
|
||||
} else {
|
||||
conversation = conversationResult.rows[0];
|
||||
}
|
||||
conversation = newConv.rows[0];
|
||||
} else {
|
||||
conversation = conversationResult.rows[0];
|
||||
}
|
||||
// 3. Сохраняем сообщение с conversation_id
|
||||
let result;
|
||||
if (messageType === 'admin_chat') {
|
||||
// Для админских сообщений добавляем sender_id
|
||||
result = await db.getQuery()(
|
||||
`INSERT INTO messages (conversation_id, user_id, sender_id, sender_type_encrypted, content_encrypted, channel_encrypted, role_encrypted, direction_encrypted, message_type, created_at, attachment_filename_encrypted, attachment_mimetype_encrypted, attachment_size, attachment_data)
|
||||
VALUES ($1,$2,$3,encrypt_text($4,$13),encrypt_text($5,$13),encrypt_text($6,$13),encrypt_text($7,$13),encrypt_text($8,$13),$9,NOW(),encrypt_text($10,$13),encrypt_text($11,$13),$12,$14) RETURNING *`,
|
||||
[conversation.id, user_id, senderId, sender_type, content, channel, role, direction, messageType, attachment_filename, attachment_mimetype, attachment_size, attachment_data, encryptionKey]
|
||||
);
|
||||
} else {
|
||||
// Для обычных сообщений без sender_id
|
||||
result = await db.getQuery()(
|
||||
`INSERT INTO messages (user_id, conversation_id, sender_type_encrypted, content_encrypted, channel_encrypted, role_encrypted, direction_encrypted, message_type, created_at, attachment_filename_encrypted, attachment_mimetype_encrypted, attachment_size, attachment_data)
|
||||
VALUES ($1,$2,encrypt_text($3,$12),encrypt_text($4,$12),encrypt_text($5,$12),encrypt_text($6,$12),encrypt_text($7,$12),$13,NOW(),encrypt_text($8,$12),encrypt_text($9,$12),$10,$11) RETURNING *`,
|
||||
[user_id, conversation.id, sender_type, content, channel, role, direction, messageType, attachment_filename, attachment_mimetype, attachment_size, attachment_data, encryptionKey]
|
||||
);
|
||||
}
|
||||
const result = await db.getQuery()(
|
||||
`INSERT INTO messages (user_id, conversation_id, sender_type_encrypted, content_encrypted, channel_encrypted, role_encrypted, direction_encrypted, created_at, attachment_filename_encrypted, attachment_mimetype_encrypted, attachment_size, attachment_data)
|
||||
VALUES ($1,$2,encrypt_text($3,$12),encrypt_text($4,$12),encrypt_text($5,$12),encrypt_text($6,$12),encrypt_text($7,$12),NOW(),encrypt_text($8,$12),encrypt_text($9,$12),$10,$11) RETURNING *`,
|
||||
[user_id, conversation.id, sender_type, content, channel, role, direction, attachment_filename, attachment_mimetype, attachment_size, attachment_data, encryptionKey]
|
||||
);
|
||||
// 4. Если это исходящее сообщение для Telegram — отправляем через бота
|
||||
if (channel === 'telegram' && direction === 'out') {
|
||||
try {
|
||||
@@ -228,10 +135,15 @@ router.post('/', async (req, res) => {
|
||||
if (tgIdentity.rows.length > 0) {
|
||||
const telegramId = tgIdentity.rows[0].provider_id;
|
||||
// console.log(`[messages.js] Отправка сообщения в Telegram ID: ${telegramId}, текст: ${content}`);
|
||||
const bot = await telegramBot.getBot();
|
||||
try {
|
||||
const sendResult = await bot.telegram.sendMessage(telegramId, content);
|
||||
// console.log(`[messages.js] Результат отправки в Telegram:`, sendResult);
|
||||
const telegramBot = botManager.getBot('telegram');
|
||||
if (telegramBot && telegramBot.isInitialized) {
|
||||
const bot = telegramBot.getBot();
|
||||
const sendResult = await bot.telegram.sendMessage(telegramId, content);
|
||||
// console.log(`[messages.js] Результат отправки в Telegram:`, sendResult);
|
||||
} else {
|
||||
logger.warn('[messages.js] Telegram Bot не инициализирован');
|
||||
}
|
||||
} catch (sendErr) {
|
||||
// console.error(`[messages.js] Ошибка при отправке в Telegram:`, sendErr);
|
||||
}
|
||||
@@ -252,16 +164,18 @@ router.post('/', async (req, res) => {
|
||||
);
|
||||
if (emailIdentity.rows.length > 0) {
|
||||
const email = emailIdentity.rows[0].provider_id;
|
||||
await emailBot.sendEmail(email, 'Новое сообщение', content);
|
||||
const emailBot = botManager.getBot('email');
|
||||
if (emailBot && emailBot.isInitialized) {
|
||||
await emailBot.sendEmail(email, 'Новое сообщение', content);
|
||||
} else {
|
||||
logger.warn('[messages.js] Email Bot не инициализирован для отправки');
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// console.error('[messages.js] Ошибка отправки email:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Отправляем WebSocket уведомления
|
||||
broadcastMessagesUpdate();
|
||||
|
||||
res.json({ success: true, message: result.rows[0] });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: 'DB error', details: e.message });
|
||||
@@ -274,8 +188,7 @@ router.post('/mark-read', async (req, res) => {
|
||||
// console.log('[DEBUG] /mark-read req.user:', req.user);
|
||||
// console.log('[DEBUG] /mark-read req.body:', req.body);
|
||||
const adminId = req.user && req.user.id;
|
||||
const { userId, lastReadAt, messageType = 'user_chat' } = req.body;
|
||||
|
||||
const { userId, lastReadAt } = req.body;
|
||||
if (!adminId) {
|
||||
// console.error('[ERROR] /mark-read: adminId (req.user.id) is missing');
|
||||
return res.status(401).json({ error: 'Unauthorized: adminId missing' });
|
||||
@@ -284,30 +197,12 @@ router.post('/mark-read', async (req, res) => {
|
||||
// console.error('[ERROR] /mark-read: userId or lastReadAt missing');
|
||||
return res.status(400).json({ error: 'userId and lastReadAt required' });
|
||||
}
|
||||
|
||||
// Логика зависит от типа сообщения
|
||||
if (messageType === 'user_chat') {
|
||||
// Обновляем глобальный статус для всех админов
|
||||
await db.query(`
|
||||
INSERT INTO global_read_status (user_id, last_read_at, updated_by_admin_id)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT (user_id) DO UPDATE SET
|
||||
last_read_at = EXCLUDED.last_read_at,
|
||||
updated_by_admin_id = EXCLUDED.updated_by_admin_id,
|
||||
updated_at = NOW()
|
||||
`, [userId, lastReadAt, adminId]);
|
||||
} else if (messageType === 'admin_chat') {
|
||||
// Обновляем персональный статус для админских сообщений
|
||||
await db.query(`
|
||||
INSERT INTO admin_read_messages (admin_id, user_id, last_read_at)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT (admin_id, user_id) DO UPDATE SET last_read_at = EXCLUDED.last_read_at
|
||||
`, [adminId, userId, lastReadAt]);
|
||||
} else {
|
||||
return res.status(400).json({ error: 'Invalid messageType. Must be "user_chat" or "admin_chat"' });
|
||||
}
|
||||
|
||||
res.json({ success: true, messageType });
|
||||
await db.query(`
|
||||
INSERT INTO admin_read_messages (admin_id, user_id, last_read_at)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT (admin_id, user_id) DO UPDATE SET last_read_at = EXCLUDED.last_read_at
|
||||
`, [adminId, userId, lastReadAt]);
|
||||
res.json({ success: true });
|
||||
} catch (e) {
|
||||
// console.error('[ERROR] /mark-read:', e);
|
||||
res.status(500).json({ error: e.message });
|
||||
@@ -321,24 +216,11 @@ router.get('/read-status', async (req, res) => {
|
||||
// console.log('[DEBUG] /read-status req.session:', req.session);
|
||||
// console.log('[DEBUG] /read-status req.session.userId:', req.session && req.session.userId);
|
||||
const adminId = req.user && req.user.id;
|
||||
const { messageType = 'user_chat' } = req.query;
|
||||
|
||||
if (!adminId) {
|
||||
// console.error('[ERROR] /read-status: adminId (req.user.id) is missing');
|
||||
return res.status(401).json({ error: 'Unauthorized: adminId missing' });
|
||||
}
|
||||
|
||||
let result;
|
||||
if (messageType === 'user_chat') {
|
||||
// Возвращаем глобальный статус для сообщений с пользователями
|
||||
result = await db.query('SELECT user_id, last_read_at FROM global_read_status');
|
||||
} else if (messageType === 'admin_chat') {
|
||||
// Возвращаем персональный статус для админских сообщений
|
||||
result = await db.query('SELECT user_id, last_read_at FROM admin_read_messages WHERE admin_id = $1', [adminId]);
|
||||
} else {
|
||||
return res.status(400).json({ error: 'Invalid messageType. Must be "user_chat" or "admin_chat"' });
|
||||
}
|
||||
|
||||
const result = await db.query('SELECT user_id, last_read_at FROM admin_read_messages WHERE admin_id = $1', [adminId]);
|
||||
// console.log('[DEBUG] /read-status SQL result:', result.rows);
|
||||
const map = {};
|
||||
for (const row of result.rows) {
|
||||
@@ -374,19 +256,9 @@ router.post('/conversations', async (req, res) => {
|
||||
const { userId, title } = req.body;
|
||||
if (!userId) return res.status(400).json({ error: 'userId required' });
|
||||
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = path.join(__dirname, '../ssl/keys/full_db_encryption.key');
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
try {
|
||||
const conversationTitle = title || `Чат с пользователем ${userId}`;
|
||||
@@ -407,19 +279,9 @@ router.post('/broadcast', async (req, res) => {
|
||||
return res.status(400).json({ error: 'user_id и content обязательны' });
|
||||
}
|
||||
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = path.join(__dirname, '../ssl/keys/full_db_encryption.key');
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
try {
|
||||
// Получаем все идентификаторы пользователя
|
||||
@@ -450,15 +312,21 @@ router.post('/broadcast', async (req, res) => {
|
||||
const email = identities.find(i => i.provider === 'email')?.provider_id;
|
||||
if (email) {
|
||||
try {
|
||||
await emailBot.sendEmail(email, 'Новое сообщение', content);
|
||||
// Сохраняем в messages с conversation_id
|
||||
await db.getQuery()(
|
||||
`INSERT INTO messages (user_id, conversation_id, sender_type_encrypted, content_encrypted, channel_encrypted, role_encrypted, direction_encrypted, message_type, created_at)
|
||||
VALUES ($1, $2, encrypt_text($3, $8), encrypt_text($4, $8), encrypt_text($5, $8), encrypt_text($6, $8), encrypt_text($7, $8), $9, NOW())`,
|
||||
[user_id, conversation.id, 'admin', content, 'email', 'user', 'out', 'user_chat', encryptionKey]
|
||||
);
|
||||
results.push({ channel: 'email', status: 'sent' });
|
||||
sent = true;
|
||||
const emailBot = botManager.getBot('email');
|
||||
if (emailBot && emailBot.isInitialized) {
|
||||
await emailBot.sendEmail(email, 'Новое сообщение', content);
|
||||
// Сохраняем в messages с conversation_id
|
||||
await db.getQuery()(
|
||||
`INSERT INTO messages (user_id, conversation_id, sender_type_encrypted, content_encrypted, channel_encrypted, role_encrypted, direction_encrypted, message_type, created_at)
|
||||
VALUES ($1, $2, encrypt_text($3, $8), encrypt_text($4, $8), encrypt_text($5, $8), encrypt_text($6, $8), encrypt_text($7, $8), $9, NOW())`,
|
||||
[user_id, conversation.id, 'admin', content, 'email', 'user', 'out', encryptionKey, 'user_chat']
|
||||
);
|
||||
results.push({ channel: 'email', status: 'sent' });
|
||||
sent = true;
|
||||
} else {
|
||||
logger.warn('[messages.js] Email Bot не инициализирован');
|
||||
results.push({ channel: 'email', status: 'error', error: 'Bot not initialized' });
|
||||
}
|
||||
} catch (err) {
|
||||
results.push({ channel: 'email', status: 'error', error: err.message });
|
||||
}
|
||||
@@ -467,15 +335,21 @@ router.post('/broadcast', async (req, res) => {
|
||||
const telegram = identities.find(i => i.provider === 'telegram')?.provider_id;
|
||||
if (telegram) {
|
||||
try {
|
||||
const bot = await telegramBot.getBot();
|
||||
await bot.telegram.sendMessage(telegram, content);
|
||||
await db.getQuery()(
|
||||
`INSERT INTO messages (user_id, conversation_id, sender_type_encrypted, content_encrypted, channel_encrypted, role_encrypted, direction_encrypted, message_type, created_at)
|
||||
VALUES ($1, $2, encrypt_text($3, $8), encrypt_text($4, $8), encrypt_text($5, $8), encrypt_text($6, $8), encrypt_text($7, $8), $9, NOW())`,
|
||||
[user_id, conversation.id, 'admin', content, 'telegram', 'user', 'out', 'user_chat', encryptionKey]
|
||||
);
|
||||
results.push({ channel: 'telegram', status: 'sent' });
|
||||
sent = true;
|
||||
const telegramBot = botManager.getBot('telegram');
|
||||
if (telegramBot && telegramBot.isInitialized) {
|
||||
const bot = telegramBot.getBot();
|
||||
await bot.telegram.sendMessage(telegram, content);
|
||||
await db.getQuery()(
|
||||
`INSERT INTO messages (user_id, conversation_id, sender_type_encrypted, content_encrypted, channel_encrypted, role_encrypted, direction_encrypted, message_type, created_at)
|
||||
VALUES ($1, $2, encrypt_text($3, $8), encrypt_text($4, $8), encrypt_text($5, $8), encrypt_text($6, $8), encrypt_text($7, $8), $9, NOW())`,
|
||||
[user_id, conversation.id, 'admin', content, 'telegram', 'user', 'out', encryptionKey, 'user_chat']
|
||||
);
|
||||
results.push({ channel: 'telegram', status: 'sent' });
|
||||
sent = true;
|
||||
} else {
|
||||
logger.warn('[messages.js] Telegram Bot не инициализирован');
|
||||
results.push({ channel: 'telegram', status: 'error', error: 'Bot not initialized' });
|
||||
}
|
||||
} catch (err) {
|
||||
results.push({ channel: 'telegram', status: 'error', error: err.message });
|
||||
}
|
||||
@@ -520,13 +394,19 @@ router.delete('/history/:userId', async (req, res) => {
|
||||
[userId]
|
||||
);
|
||||
|
||||
// Удаляем хеши дедупликации для этого пользователя
|
||||
const dedupResult = await db.getQuery()(
|
||||
'DELETE FROM message_deduplication WHERE user_id = $1 RETURNING id',
|
||||
[userId]
|
||||
);
|
||||
|
||||
// Удаляем беседы пользователя (если есть)
|
||||
const conversationResult = await db.getQuery()(
|
||||
'DELETE FROM conversations WHERE user_id = $1 RETURNING id',
|
||||
[userId]
|
||||
);
|
||||
|
||||
console.log(`[messages.js] Deleted ${result.rowCount} messages and ${conversationResult.rowCount} conversations for user ${userId}`);
|
||||
console.log(`[messages.js] Deleted ${result.rowCount} messages, ${dedupResult.rowCount} deduplication hashes, and ${conversationResult.rowCount} conversations for user ${userId}`);
|
||||
|
||||
// Отправляем обновление через WebSocket
|
||||
broadcastMessagesUpdate();
|
||||
@@ -542,254 +422,4 @@ router.delete('/history/:userId', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/messages/admin/send - отправка сообщения админу
|
||||
router.post('/admin/send', async (req, res) => {
|
||||
try {
|
||||
const adminId = req.user && req.user.id;
|
||||
const { recipientAdminId, content } = req.body;
|
||||
|
||||
if (!adminId) {
|
||||
return res.status(401).json({ error: 'Unauthorized: adminId missing' });
|
||||
}
|
||||
if (!recipientAdminId || !content) {
|
||||
return res.status(400).json({ error: 'recipientAdminId and content required' });
|
||||
}
|
||||
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = path.join(__dirname, '../ssl/keys/full_db_encryption.key');
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
|
||||
// Ищем существующую приватную беседу между двумя админами через conversation_participants
|
||||
let conversationResult = await db.getQuery()(`
|
||||
SELECT c.id
|
||||
FROM conversations c
|
||||
INNER JOIN conversation_participants cp1 ON cp1.conversation_id = c.id AND cp1.user_id = $1
|
||||
INNER JOIN conversation_participants cp2 ON cp2.conversation_id = c.id AND cp2.user_id = $2
|
||||
WHERE c.conversation_type = 'admin_chat'
|
||||
LIMIT 1
|
||||
`, [adminId, recipientAdminId]);
|
||||
|
||||
let conversationId;
|
||||
if (conversationResult.rows.length === 0) {
|
||||
// Создаем новую приватную беседу между админами
|
||||
const title = `Приватная беседа ${adminId} - ${recipientAdminId}`;
|
||||
const newConv = await db.getQuery()(
|
||||
'INSERT INTO conversations (user_id, title_encrypted, conversation_type, created_at, updated_at) VALUES ($1, encrypt_text($2, $3), $4, NOW(), NOW()) RETURNING id',
|
||||
[recipientAdminId, title, encryptionKey, 'admin_chat']
|
||||
);
|
||||
conversationId = newConv.rows[0].id;
|
||||
|
||||
// Добавляем участников в беседу
|
||||
await db.getQuery()(
|
||||
'INSERT INTO conversation_participants (conversation_id, user_id) VALUES ($1, $2), ($1, $3)',
|
||||
[conversationId, adminId, recipientAdminId]
|
||||
);
|
||||
|
||||
console.log(`[admin/send] Создана новая беседа ${conversationId} между ${adminId} и ${recipientAdminId}`);
|
||||
} else {
|
||||
conversationId = conversationResult.rows[0].id;
|
||||
console.log(`[admin/send] Найдена существующая беседа ${conversationId} между ${adminId} и ${recipientAdminId}`);
|
||||
}
|
||||
|
||||
// Сохраняем сообщение с типом 'admin_chat'
|
||||
const result = await db.getQuery()(
|
||||
`INSERT INTO messages (conversation_id, user_id, sender_id, sender_type_encrypted, content_encrypted, channel_encrypted, role_encrypted, direction_encrypted, message_type, created_at)
|
||||
VALUES ($1, $2, $3, encrypt_text($4, $9), encrypt_text($5, $9), encrypt_text($6, $9), encrypt_text($7, $9), encrypt_text($8, $9), $10, NOW()) RETURNING id`,
|
||||
[conversationId, recipientAdminId, adminId, 'admin', content, 'web', 'admin', 'out', encryptionKey, 'admin_chat']
|
||||
);
|
||||
|
||||
// Отправляем WebSocket уведомления
|
||||
broadcastMessagesUpdate();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
messageId: result.rows[0].id,
|
||||
conversationId,
|
||||
messageType: 'admin_chat'
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('[ERROR] /admin/send:', e);
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/messages/admin/conversations - получить личные чаты админа
|
||||
router.get('/admin/conversations', async (req, res) => {
|
||||
try {
|
||||
const adminId = req.user && req.user.id;
|
||||
|
||||
if (!adminId) {
|
||||
return res.status(401).json({ error: 'Unauthorized: adminId missing' });
|
||||
}
|
||||
|
||||
// Получаем список админов, с которыми есть переписка
|
||||
const conversations = await db.query(`
|
||||
SELECT DISTINCT
|
||||
CASE
|
||||
WHEN sender_type = 'admin' AND user_id != $1 THEN user_id
|
||||
ELSE sender_id
|
||||
END as admin_id,
|
||||
MAX(created_at) as last_message_at
|
||||
FROM messages
|
||||
WHERE message_type = 'admin_chat'
|
||||
AND (user_id = $1 OR sender_id = $1)
|
||||
GROUP BY admin_id
|
||||
ORDER BY last_message_at DESC
|
||||
`, [adminId]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
conversations: conversations.rows
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('[ERROR] /admin/conversations:', e);
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/messages/admin/contacts - получить админов для приватного чата
|
||||
router.get('/admin/contacts', async (req, res) => {
|
||||
try {
|
||||
const adminId = req.user && req.user.id;
|
||||
|
||||
if (!adminId) {
|
||||
return res.status(401).json({ error: 'Unauthorized: adminId missing' });
|
||||
}
|
||||
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = path.join(__dirname, '../ssl/keys/full_db_encryption.key');
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
|
||||
// Получаем всех пользователей, с которыми есть приватные беседы через conversation_participants
|
||||
const adminContacts = await db.getQuery()(`
|
||||
SELECT DISTINCT
|
||||
other_user.id,
|
||||
COALESCE(
|
||||
decrypt_text(other_user.first_name_encrypted, $2),
|
||||
decrypt_text(other_user.username_encrypted, $2),
|
||||
'Пользователь ' || other_user.id
|
||||
) as name,
|
||||
'admin@system' as email,
|
||||
CASE
|
||||
WHEN other_user.role = 'editor' THEN 'admin'
|
||||
WHEN other_user.role = 'readonly' THEN 'admin'
|
||||
ELSE 'user'
|
||||
END as contact_type,
|
||||
MAX(m.created_at) as last_message_at,
|
||||
COUNT(m.id) as message_count
|
||||
FROM conversations c
|
||||
INNER JOIN conversation_participants cp_current ON cp_current.conversation_id = c.id AND cp_current.user_id = $1
|
||||
INNER JOIN conversation_participants cp_other ON cp_other.conversation_id = c.id AND cp_other.user_id != $1
|
||||
INNER JOIN users other_user ON other_user.id = cp_other.user_id
|
||||
LEFT JOIN messages m ON m.conversation_id = c.id AND m.message_type = 'admin_chat'
|
||||
WHERE c.conversation_type = 'admin_chat'
|
||||
GROUP BY
|
||||
other_user.id,
|
||||
other_user.first_name_encrypted,
|
||||
other_user.username_encrypted,
|
||||
other_user.role
|
||||
ORDER BY MAX(m.created_at) DESC
|
||||
`, [adminId, encryptionKey]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
contacts: adminContacts.rows.map(contact => ({
|
||||
...contact,
|
||||
created_at: contact.last_message_at, // Используем время последнего сообщения как время создания для сортировки
|
||||
telegram: null,
|
||||
wallet: null
|
||||
}))
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('[ERROR] /admin/contacts:', e);
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/messages/admin/:adminId - получить сообщения с конкретным админом
|
||||
router.get('/admin/:adminId', async (req, res) => {
|
||||
try {
|
||||
const currentAdminId = req.user && req.user.id;
|
||||
const { adminId } = req.params;
|
||||
|
||||
if (!currentAdminId) {
|
||||
return res.status(401).json({ error: 'Unauthorized: adminId missing' });
|
||||
}
|
||||
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = path.join(__dirname, '../ssl/keys/full_db_encryption.key');
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
|
||||
// Получаем сообщения из приватной беседы между админами через conversation_participants
|
||||
const result = await db.getQuery()(
|
||||
`SELECT m.id, m.user_id, m.sender_id,
|
||||
decrypt_text(m.sender_type_encrypted, $3) as sender_type,
|
||||
decrypt_text(m.content_encrypted, $3) as content,
|
||||
decrypt_text(m.channel_encrypted, $3) as channel,
|
||||
decrypt_text(m.role_encrypted, $3) as role,
|
||||
decrypt_text(m.direction_encrypted, $3) as direction,
|
||||
m.created_at, m.message_type,
|
||||
-- Получаем wallet адреса отправителей (расшифровываем provider_id_encrypted)
|
||||
CASE
|
||||
WHEN sender_ui.provider_encrypted = encrypt_text('wallet', $3)
|
||||
THEN decrypt_text(sender_ui.provider_id_encrypted, $3)
|
||||
ELSE 'Админ'
|
||||
END as sender_wallet,
|
||||
CASE
|
||||
WHEN recipient_ui.provider_encrypted = encrypt_text('wallet', $3)
|
||||
THEN decrypt_text(recipient_ui.provider_id_encrypted, $3)
|
||||
ELSE 'Админ'
|
||||
END as recipient_wallet
|
||||
FROM messages m
|
||||
INNER JOIN conversations c ON c.id = m.conversation_id
|
||||
INNER JOIN conversation_participants cp1 ON cp1.conversation_id = c.id AND cp1.user_id = $1
|
||||
INNER JOIN conversation_participants cp2 ON cp2.conversation_id = c.id AND cp2.user_id = $2
|
||||
LEFT JOIN user_identities sender_ui ON sender_ui.user_id = m.sender_id
|
||||
LEFT JOIN user_identities recipient_ui ON recipient_ui.user_id = m.user_id
|
||||
WHERE m.message_type = 'admin_chat' AND c.conversation_type = 'admin_chat'
|
||||
ORDER BY m.created_at ASC`,
|
||||
[currentAdminId, adminId, encryptionKey]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
messages: result.rows,
|
||||
messageType: 'admin_chat'
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('[ERROR] /admin/:adminId:', e);
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -16,8 +16,8 @@ const axios = require('axios');
|
||||
const db = require('../db');
|
||||
const aiAssistant = require('../services/ai-assistant');
|
||||
const aiCache = require('../services/ai-cache');
|
||||
const aiQueue = require('../services/ai-queue');
|
||||
const logger = require('../utils/logger');
|
||||
const ollamaConfig = require('../services/ollamaConfig');
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
const results = {};
|
||||
@@ -37,7 +37,8 @@ router.get('/', async (req, res) => {
|
||||
|
||||
// Ollama
|
||||
try {
|
||||
const ollama = await axios.get(process.env.OLLAMA_BASE_URL ? process.env.OLLAMA_BASE_URL + '/api/tags' : 'http://ollama:11434/api/tags', { timeout: 2000 });
|
||||
const ollamaConfig = require('../services/ollamaConfig');
|
||||
const ollama = await axios.get(ollamaConfig.getApiUrl('tags'), { timeout: 2000 });
|
||||
results.ollama = { status: 'ok', models: ollama.data.models?.length || 0 };
|
||||
} catch (e) {
|
||||
results.ollama = { status: 'error', error: e.message };
|
||||
@@ -57,25 +58,27 @@ router.get('/', async (req, res) => {
|
||||
// GET /api/monitoring/ai-stats - статистика AI
|
||||
router.get('/ai-stats', async (req, res) => {
|
||||
try {
|
||||
const aiHealth = await aiAssistant.checkHealth();
|
||||
const cacheStats = aiCache.getStats();
|
||||
const queueStats = aiQueue.getStats();
|
||||
|
||||
res.json({
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString(),
|
||||
ai: {
|
||||
health: aiHealth,
|
||||
model: process.env.OLLAMA_MODEL || 'qwen2.5:7b',
|
||||
baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434'
|
||||
health: 'ok',
|
||||
model: ollamaConfig.getDefaultModel(),
|
||||
baseUrl: ollamaConfig.getBaseUrl()
|
||||
},
|
||||
cache: {
|
||||
...cacheStats,
|
||||
hitRate: `${(cacheStats.hitRate * 100).toFixed(1)}%`
|
||||
size: 0,
|
||||
maxSize: 100,
|
||||
hitRate: 0
|
||||
},
|
||||
queue: {
|
||||
...queueStats,
|
||||
avgResponseTime: `${queueStats.avgResponseTime.toFixed(0)}ms`
|
||||
totalAdded: 0,
|
||||
totalProcessed: 0,
|
||||
totalFailed: 0,
|
||||
averageProcessingTime: 0,
|
||||
currentQueueSize: 0,
|
||||
lastProcessedAt: null,
|
||||
uptime: 0
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -107,7 +110,7 @@ router.post('/ai-cache/clear', async (req, res) => {
|
||||
// POST /api/monitoring/ai-queue/clear - очистка очереди
|
||||
router.post('/ai-queue/clear', async (req, res) => {
|
||||
try {
|
||||
aiQueue.clear();
|
||||
aiAssistant.aiQueue.clearQueue();
|
||||
res.json({
|
||||
status: 'ok',
|
||||
message: 'AI queue cleared successfully'
|
||||
|
||||
@@ -22,7 +22,8 @@ const { requireAuth } = require('../middleware/auth');
|
||||
router.get('/status', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const axios = require('axios');
|
||||
const ollamaUrl = process.env.OLLAMA_BASE_URL || 'http://ollama:11434';
|
||||
const ollamaConfig = require('../services/ollamaConfig');
|
||||
const ollamaUrl = ollamaConfig.getBaseUrl();
|
||||
|
||||
// Проверяем API Ollama через HTTP запрос
|
||||
try {
|
||||
@@ -54,7 +55,8 @@ router.get('/status', requireAuth, async (req, res) => {
|
||||
router.get('/models', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const axios = require('axios');
|
||||
const ollamaUrl = process.env.OLLAMA_BASE_URL || 'http://ollama:11434';
|
||||
const ollamaConfig = require('../services/ollamaConfig');
|
||||
const ollamaUrl = ollamaConfig.getBaseUrl();
|
||||
|
||||
const response = await axios.get(`${ollamaUrl}/api/tags`, {
|
||||
timeout: 5000
|
||||
|
||||
@@ -22,18 +22,9 @@ async function ensureAdminPagesTable(fields) {
|
||||
const tableName = `admin_pages_simple`;
|
||||
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = path.join(__dirname, '../ssl/keys/full_db_encryption.key');
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
// console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
// Проверяем, есть ли таблица
|
||||
const existsRes = await db.getQuery()(
|
||||
@@ -131,18 +122,9 @@ router.get('/', async (req, res) => {
|
||||
const tableName = `admin_pages_simple`;
|
||||
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = path.join(__dirname, '../ssl/keys/full_db_encryption.key');
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
// console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
// Проверяем, есть ли таблица
|
||||
const existsRes = await db.getQuery()(
|
||||
|
||||
@@ -49,9 +49,7 @@ const aiAssistant = require('../services/ai-assistant');
|
||||
const dns = require('node:dns').promises;
|
||||
const aiAssistantSettingsService = require('../services/aiAssistantSettingsService');
|
||||
const aiAssistantRulesService = require('../services/aiAssistantRulesService');
|
||||
const telegramBot = require('../services/telegramBot');
|
||||
const EmailBotService = require('../services/emailBot');
|
||||
const emailBotService = new EmailBotService();
|
||||
const botsSettings = require('../services/botsSettings');
|
||||
const dbSettingsService = require('../services/dbSettingsService');
|
||||
const { broadcastAuthTokenAdded, broadcastAuthTokenDeleted, broadcastAuthTokenUpdated } = require('../wsHub');
|
||||
|
||||
@@ -76,16 +74,9 @@ router.get('/rpc', async (req, res, next) => {
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = path.join(__dirname, '../ssl/keys/full_db_encryption.key');
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
const rpcProvidersResult = await db.getQuery()(
|
||||
'SELECT id, chain_id, created_at, updated_at, decrypt_text(network_id_encrypted, $1) as network_id, decrypt_text(rpc_url_encrypted, $1) as rpc_url FROM rpc_providers',
|
||||
@@ -165,16 +156,9 @@ router.get('/auth-tokens', async (req, res, next) => {
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = path.join(__dirname, '../ssl/keys/full_db_encryption.key');
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
const tokensResult = await db.getQuery()(
|
||||
'SELECT id, min_balance, readonly_threshold, editor_threshold, created_at, updated_at, decrypt_text(name_encrypted, $1) as name, decrypt_text(address_encrypted, $1) as address, decrypt_text(network_encrypted, $1) as network FROM auth_tokens',
|
||||
@@ -510,7 +494,7 @@ router.delete('/ai-assistant-rules/:id', requireAdmin, async (req, res, next) =>
|
||||
// Получить текущие настройки Email (для страницы Email)
|
||||
router.get('/email-settings', requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const settings = await emailBotService.getSettingsFromDb();
|
||||
const settings = await botsSettings.getEmailSettings();
|
||||
res.json({ success: true, settings });
|
||||
} catch (error) {
|
||||
res.status(404).json({ success: false, error: error.message });
|
||||
@@ -556,7 +540,7 @@ router.put('/email-settings', requireAdmin, async (req, res, next) => {
|
||||
updated_at: new Date()
|
||||
};
|
||||
|
||||
const result = await emailBotService.saveEmailSettings(settings);
|
||||
const result = await botsSettings.saveEmailSettings(settings);
|
||||
res.json({ success: true, data: result });
|
||||
} catch (error) {
|
||||
logger.error('Ошибка при обновлении email настроек:', error);
|
||||
@@ -577,11 +561,7 @@ router.post('/email-settings/test', requireAdmin, async (req, res, next) => {
|
||||
}
|
||||
|
||||
// Отправляем тестовое письмо
|
||||
const result = await emailBotService.sendEmail(
|
||||
test_email,
|
||||
'Тест Email системы DLE',
|
||||
'Это тестовое письмо для проверки работы email системы. Если вы его получили, значит настройки работают корректно!'
|
||||
);
|
||||
const result = await botsSettings.testEmailSMTP(test_email);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -597,7 +577,7 @@ router.post('/email-settings/test', requireAdmin, async (req, res, next) => {
|
||||
// Тест IMAP подключения
|
||||
router.post('/email-settings/test-imap', requireAdmin, async (req, res, next) => {
|
||||
try {
|
||||
const result = await emailBotService.testImapConnection();
|
||||
const result = await botsSettings.testEmailIMAP();
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
logger.error('Ошибка при тестировании IMAP подключения:', error);
|
||||
@@ -608,7 +588,7 @@ router.post('/email-settings/test-imap', requireAdmin, async (req, res, next) =>
|
||||
// Тест SMTP подключения
|
||||
router.post('/email-settings/test-smtp', requireAdmin, async (req, res, next) => {
|
||||
try {
|
||||
const result = await emailBotService.testSmtpConnection();
|
||||
const result = await botsSettings.testEmailSMTP();
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
logger.error('Ошибка при тестировании SMTP подключения:', error);
|
||||
@@ -619,7 +599,7 @@ router.post('/email-settings/test-smtp', requireAdmin, async (req, res, next) =>
|
||||
// Получить список всех email (для ассистента)
|
||||
router.get('/email-settings/list', requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const emails = await emailBotService.getAllEmailSettings();
|
||||
const emails = await botsSettings.getAllEmailSettings();
|
||||
res.json({ success: true, items: emails });
|
||||
} catch (error) {
|
||||
res.status(404).json({ success: false, error: error.message });
|
||||
@@ -629,7 +609,7 @@ router.get('/email-settings/list', requireAdmin, async (req, res) => {
|
||||
// Получить текущие настройки Telegram-бота (для страницы Telegram)
|
||||
router.get('/telegram-settings', requireAdmin, async (req, res, next) => {
|
||||
try {
|
||||
const settings = await telegramBot.getTelegramSettings();
|
||||
const settings = await botsSettings.getTelegramSettings();
|
||||
res.json({ success: true, settings });
|
||||
} catch (error) {
|
||||
res.status(404).json({ success: false, error: error.message });
|
||||
@@ -657,7 +637,7 @@ router.put('/telegram-settings', requireAdmin, async (req, res, next) => {
|
||||
updated_at: new Date()
|
||||
};
|
||||
|
||||
const result = await telegramBot.saveTelegramSettings(settings);
|
||||
const result = await botsSettings.saveTelegramSettings(settings);
|
||||
res.json({ success: true, data: result });
|
||||
} catch (error) {
|
||||
logger.error('Ошибка при обновлении настроек Telegram:', error);
|
||||
@@ -668,7 +648,7 @@ router.put('/telegram-settings', requireAdmin, async (req, res, next) => {
|
||||
// Получить список всех Telegram-ботов (для ассистента)
|
||||
router.get('/telegram-settings/list', requireAdmin, async (req, res, next) => {
|
||||
try {
|
||||
const bots = await telegramBot.getAllBots();
|
||||
const bots = await botsSettings.getAllTelegramBots();
|
||||
res.json({ success: true, items: bots });
|
||||
} catch (error) {
|
||||
res.status(404).json({ success: false, error: error.message });
|
||||
|
||||
@@ -1,99 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is proprietary and confidential.
|
||||
* Unauthorized copying, modification, or distribution is prohibited.
|
||||
*
|
||||
* For licensing inquiries: info@hb3-accelerator.com
|
||||
* Website: https://hb3-accelerator.com
|
||||
* GitHub: https://github.com/HB3-ACCELERATOR
|
||||
* Системные endpoints для управления готовностью системы
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const memoryMonitor = require('../utils/memoryMonitor');
|
||||
const logger = require('../utils/logger');
|
||||
const { checkAdminRole } = require('../services/admin-role');
|
||||
|
||||
// Middleware для проверки прав администратора
|
||||
const requireAdmin = async (req, res, next) => {
|
||||
/**
|
||||
* HTTP fallback endpoint для Ollama контейнера
|
||||
* Используется когда WebSocket недоступен
|
||||
*/
|
||||
router.post('/ollama-ready', async (req, res) => {
|
||||
try {
|
||||
if (!req.session || !req.session.userId) {
|
||||
return res.status(401).json({ success: false, error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
const isAdmin = await checkAdminRole(req.session.userId);
|
||||
if (!isAdmin) {
|
||||
return res.status(403).json({ success: false, error: 'Admin access required' });
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
logger.error('Error checking admin role:', error);
|
||||
res.status(500).json({ success: false, error: 'Internal server error' });
|
||||
}
|
||||
};
|
||||
|
||||
// GET /api/system/memory - Получить информацию о памяти
|
||||
router.get('/memory', requireAdmin, (req, res) => {
|
||||
try {
|
||||
const memoryUsage = memoryMonitor.getMemoryUsage();
|
||||
logger.info('[System] 🔌 Ollama готов к работе');
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
memory: memoryUsage,
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
message: 'Ollama готов'
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error getting memory usage:', error);
|
||||
res.status(500).json({ success: false, error: 'Failed to get memory usage' });
|
||||
logger.error('[System] ❌ Ошибка:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/system/memory/start - Запустить мониторинг памяти
|
||||
router.post('/memory/start', requireAdmin, (req, res) => {
|
||||
/**
|
||||
* Endpoint для проверки статуса системы
|
||||
*/
|
||||
router.get('/status', (req, res) => {
|
||||
try {
|
||||
const { interval } = req.body;
|
||||
memoryMonitor.start(interval || 60000);
|
||||
res.json({ success: true, message: 'Memory monitoring started' });
|
||||
} catch (error) {
|
||||
logger.error('Error starting memory monitoring:', error);
|
||||
res.status(500).json({ success: false, error: 'Failed to start memory monitoring' });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/system/memory/stop - Остановить мониторинг памяти
|
||||
router.post('/memory/stop', requireAdmin, (req, res) => {
|
||||
try {
|
||||
memoryMonitor.stop();
|
||||
res.json({ success: true, message: 'Memory monitoring stopped' });
|
||||
} catch (error) {
|
||||
logger.error('Error stopping memory monitoring:', error);
|
||||
res.status(500).json({ success: false, error: 'Failed to stop memory monitoring' });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/system/health - Проверка здоровья системы
|
||||
router.get('/health', (req, res) => {
|
||||
try {
|
||||
const memoryUsage = memoryMonitor.getMemoryUsage();
|
||||
const uptime = process.uptime();
|
||||
|
||||
const botManager = require('../services/botManager');
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
status: 'healthy',
|
||||
uptime: Math.round(uptime),
|
||||
memory: memoryUsage,
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
systemReady: true, // Система всегда готова после запуска
|
||||
botsInitialized: botManager.isInitialized,
|
||||
bots: botManager.getStatus(),
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Error getting system health:', error);
|
||||
res.status(500).json({ success: false, error: 'Failed to get system health' });
|
||||
logger.error('[System] Ошибка получения статуса:', error);
|
||||
|
||||
res.status(500).json({
|
||||
error: error.message,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
@@ -21,14 +21,8 @@ const { broadcastTableUpdate, broadcastTableRelationsUpdate } = require('../wsHu
|
||||
|
||||
// Вспомогательная функция для получения ключа шифрования
|
||||
function getEncryptionKey() {
|
||||
const fs = require('fs');
|
||||
const keyPath = '/app/ssl/keys/full_db_encryption.key';
|
||||
|
||||
if (!fs.existsSync(keyPath)) {
|
||||
throw new Error('Encryption key file not found');
|
||||
}
|
||||
|
||||
return fs.readFileSync(keyPath, 'utf8').trim();
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
return encryptionUtils.getEncryptionKey();
|
||||
}
|
||||
|
||||
router.use((req, res, next) => {
|
||||
@@ -39,14 +33,9 @@ router.use((req, res, next) => {
|
||||
// Получить список всех таблиц (доступно всем)
|
||||
router.get('/', async (req, res, next) => {
|
||||
try {
|
||||
// Получаем ключ шифрования
|
||||
let encryptionKey;
|
||||
try {
|
||||
encryptionKey = getEncryptionKey();
|
||||
} catch (keyError) {
|
||||
// console.error('Error reading encryption key:', keyError);
|
||||
return res.status(500).json({ error: 'Database encryption error' });
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
const result = await db.getQuery()('SELECT id, created_at, updated_at, is_rag_source_id, decrypt_text(name_encrypted, $1) as name, decrypt_text(description_encrypted, $1) as description FROM user_tables ORDER BY id', [encryptionKey]);
|
||||
res.json(result.rows);
|
||||
@@ -60,14 +49,9 @@ router.post('/', async (req, res, next) => {
|
||||
try {
|
||||
const { name, description, isRagSourceId } = req.body;
|
||||
|
||||
// Получаем ключ шифрования
|
||||
let encryptionKey;
|
||||
try {
|
||||
encryptionKey = getEncryptionKey();
|
||||
} catch (keyError) {
|
||||
// console.error('Error reading encryption key:', keyError);
|
||||
return res.status(500).json({ error: 'Database encryption error' });
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
const result = await db.getQuery()(
|
||||
'INSERT INTO user_tables (name_encrypted, description_encrypted, is_rag_source_id) VALUES (encrypt_text($1, $4), encrypt_text($2, $4), $3) RETURNING *',
|
||||
@@ -82,14 +66,9 @@ router.post('/', async (req, res, next) => {
|
||||
// Получить данные из таблицы is_rag_source с расшифровкой
|
||||
router.get('/rag-sources', async (req, res, next) => {
|
||||
try {
|
||||
// Получаем ключ шифрования
|
||||
let encryptionKey;
|
||||
try {
|
||||
encryptionKey = getEncryptionKey();
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
return res.status(500).json({ error: 'Database encryption error' });
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
const result = await db.getQuery()(
|
||||
'SELECT id, decrypt_text(name_encrypted, $1) as name FROM is_rag_source ORDER BY id',
|
||||
@@ -107,14 +86,9 @@ router.get('/rag-sources', async (req, res, next) => {
|
||||
router.get('/:id', async (req, res, next) => {
|
||||
try {
|
||||
const tableId = req.params.id;
|
||||
// Получаем ключ шифрования
|
||||
let encryptionKey;
|
||||
try {
|
||||
encryptionKey = getEncryptionKey();
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
return res.status(500).json({ error: 'Database encryption error' });
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
// Выполняем все 4 запроса параллельно для ускорения
|
||||
const [tableMetaResult, columnsResult, rowsResult, cellValuesResult] = await Promise.all([
|
||||
@@ -193,25 +167,9 @@ router.post('/:id/columns', async (req, res, next) => {
|
||||
finalOptions.purpose = purpose;
|
||||
}
|
||||
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey;
|
||||
try {
|
||||
encryptionKey = getEncryptionKey();
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
return res.status(500).json({ error: 'Database encryption error' });
|
||||
}
|
||||
|
||||
try {
|
||||
const keyPath = '/app/ssl/keys/full_db_encryption.key';
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
// Получаем уже существующие плейсхолдеры во всей базе данных
|
||||
const existing = (await db.getQuery()('SELECT placeholder FROM user_columns WHERE placeholder IS NOT NULL', [])).rows;
|
||||
@@ -237,25 +195,9 @@ router.post('/:id/rows', async (req, res, next) => {
|
||||
[tableId]
|
||||
);
|
||||
// console.log('[DEBUG][addRow] result.rows[0]:', result.rows[0]);
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey;
|
||||
try {
|
||||
encryptionKey = getEncryptionKey();
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
return res.status(500).json({ error: 'Database encryption error' });
|
||||
}
|
||||
|
||||
try {
|
||||
const keyPath = '/app/ssl/keys/full_db_encryption.key';
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
// Получаем все строки и значения для upsert
|
||||
const rows = (await db.getQuery()('SELECT r.id as row_id, decrypt_text(c.value_encrypted, $2) as text, decrypt_text(c2.value_encrypted, $2) as answer FROM user_rows r LEFT JOIN user_cell_values c ON c.row_id = r.id AND c.column_id = 1 LEFT JOIN user_cell_values c2 ON c2.row_id = r.id AND c2.column_id = 2 WHERE r.table_id = $1', [tableId, encryptionKey])).rows;
|
||||
@@ -277,25 +219,9 @@ router.get('/:id/rows', async (req, res, next) => {
|
||||
try {
|
||||
const tableId = req.params.id;
|
||||
const { product, tags, ...relationFilters } = req.query; // tags = "B2B,VIP", relation_{colId}=rowId
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey;
|
||||
try {
|
||||
encryptionKey = getEncryptionKey();
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
return res.status(500).json({ error: 'Database encryption error' });
|
||||
}
|
||||
|
||||
try {
|
||||
const keyPath = '/app/ssl/keys/full_db_encryption.key';
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
// Получаем все столбцы, строки и значения ячеек
|
||||
const columns = (await db.getQuery()('SELECT id, table_id, "order", created_at, updated_at, decrypt_text(name_encrypted, $2) as name, decrypt_text(type_encrypted, $2) as type, decrypt_text(placeholder_encrypted, $2) as placeholder_encrypted, placeholder FROM user_columns WHERE table_id = $1', [tableId, encryptionKey])).rows;
|
||||
@@ -368,25 +294,9 @@ router.get('/:id/rows', async (req, res, next) => {
|
||||
router.post('/cell', async (req, res, next) => {
|
||||
try {
|
||||
const { row_id, column_id, value } = req.body;
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey;
|
||||
try {
|
||||
encryptionKey = getEncryptionKey();
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
return res.status(500).json({ error: 'Database encryption error' });
|
||||
}
|
||||
|
||||
try {
|
||||
const keyPath = '/app/ssl/keys/full_db_encryption.key';
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
const result = await db.getQuery()(
|
||||
`INSERT INTO user_cell_values (row_id, column_id, value_encrypted) VALUES ($1, $2, encrypt_text($3, $4))
|
||||
@@ -438,25 +348,9 @@ router.delete('/row/:rowId', async (req, res, next) => {
|
||||
await db.getQuery()('DELETE FROM user_rows WHERE id = $1', [rowId]);
|
||||
|
||||
// Получаем все строки для rebuild
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey;
|
||||
try {
|
||||
encryptionKey = getEncryptionKey();
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
return res.status(500).json({ error: 'Database encryption error' });
|
||||
}
|
||||
|
||||
try {
|
||||
const keyPath = '/app/ssl/keys/full_db_encryption.key';
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
const rows = (await db.getQuery()('SELECT r.id as row_id, decrypt_text(c.value_encrypted, $2) as text, decrypt_text(c2.value_encrypted, $2) as answer FROM user_rows r LEFT JOIN user_cell_values c ON c.row_id = r.id AND c.column_id = 1 LEFT JOIN user_cell_values c2 ON c2.row_id = r.id AND c2.column_id = 2 WHERE r.table_id = $1', [tableId, encryptionKey])).rows;
|
||||
const rebuildRows = rows.filter(r => r.row_id && r.text).map(r => ({ row_id: r.row_id, text: r.text, metadata: { answer: r.answer } }));
|
||||
@@ -513,25 +407,9 @@ router.patch('/column/:columnId', async (req, res, next) => {
|
||||
const columnId = req.params.columnId;
|
||||
const { name, type, options, order, placeholder } = req.body;
|
||||
// Получаем table_id для проверки уникальности плейсхолдера
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey;
|
||||
try {
|
||||
encryptionKey = getEncryptionKey();
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
return res.status(500).json({ error: 'Database encryption error' });
|
||||
}
|
||||
|
||||
try {
|
||||
const keyPath = '/app/ssl/keys/full_db_encryption.key';
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
const colInfo = (await db.getQuery()('SELECT table_id, decrypt_text(name_encrypted, $2) as name FROM user_columns WHERE id = $1', [columnId, encryptionKey])).rows[0];
|
||||
if (!colInfo) return res.status(404).json({ error: 'Column not found' });
|
||||
@@ -644,24 +522,9 @@ router.post('/:id/rebuild-index', requireAuth, async (req, res, next) => {
|
||||
return res.status(403).json({ error: 'Доступ только для администратора' });
|
||||
}
|
||||
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
let encryptionKey;
|
||||
try {
|
||||
encryptionKey = getEncryptionKey();
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
return res.status(500).json({ error: 'Database encryption error' });
|
||||
}
|
||||
|
||||
try {
|
||||
const keyPath = '/app/ssl/keys/full_db_encryption.key';
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
const tableId = req.params.id;
|
||||
const { questionCol, answerCol } = await getQuestionAnswerColumnIds(tableId);
|
||||
@@ -823,25 +686,9 @@ router.delete('/:tableId/row/:rowId/relations/:relationId', async (req, res, nex
|
||||
router.get('/:id/placeholders', async (req, res, next) => {
|
||||
try {
|
||||
const tableId = req.params.id;
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey;
|
||||
try {
|
||||
encryptionKey = getEncryptionKey();
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
return res.status(500).json({ error: 'Database encryption error' });
|
||||
}
|
||||
|
||||
try {
|
||||
const keyPath = '/app/ssl/keys/full_db_encryption.key';
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
const columns = (await db.getQuery()('SELECT id, decrypt_text(name_encrypted, $2) as name, placeholder FROM user_columns WHERE table_id = $1', [tableId, encryptionKey])).rows;
|
||||
res.json(columns.map(col => ({
|
||||
|
||||
@@ -89,19 +89,9 @@ router.post('/user/:rowId/multirelations', async (req, res) => {
|
||||
const { column_id, to_table_id, to_row_ids } = req.body; // to_row_ids: массив id тегов
|
||||
if (!Array.isArray(to_row_ids)) return res.status(400).json({ error: 'to_row_ids должен быть массивом' });
|
||||
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = '/app/ssl/keys/full_db_encryption.key';
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
// console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
// Проверяем, является ли это обновлением тегов (проверяем связанную таблицу)
|
||||
const relatedTableName = (await db.getQuery()('SELECT decrypt_text(name_encrypted, $2) as name FROM user_tables WHERE id = $1', [to_table_id, encryptionKey])).rows[0];
|
||||
|
||||
@@ -81,16 +81,9 @@ router.get('/', requireAuth, async (req, res, next) => {
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = '/app/ssl/keys/full_db_encryption.key';
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
// --- Формируем условия ---
|
||||
const where = [];
|
||||
@@ -323,18 +316,8 @@ router.patch('/:id', requireAuth, async (req, res) => {
|
||||
let idx = 1;
|
||||
|
||||
// Получаем ключ шифрования один раз
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
try {
|
||||
const keyPath = '/app/ssl/keys/full_db_encryption.key';
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
console.log('Encryption key loaded:', encryptionKey.length, 'characters');
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
// Обработка поля name - разбиваем на first_name и last_name
|
||||
if (name !== undefined) {
|
||||
@@ -413,31 +396,11 @@ router.get('/:id', async (req, res, next) => {
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = '/app/ssl/keys/full_db_encryption.key';
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
try {
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = path.join(__dirname, '../ssl/keys/full_db_encryption.key');
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
|
||||
const query = db.getQuery();
|
||||
// Получаем пользователя
|
||||
@@ -485,16 +448,9 @@ router.post('/', async (req, res) => {
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = '/app/ssl/keys/full_db_encryption.key';
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
try {
|
||||
const result = await db.getQuery()(
|
||||
@@ -514,16 +470,9 @@ router.post('/import', requireAuth, async (req, res) => {
|
||||
// Получаем ключ шифрования
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
let encryptionKey = 'default-key';
|
||||
|
||||
try {
|
||||
const keyPath = '/app/ssl/keys/full_db_encryption.key';
|
||||
if (fs.existsSync(keyPath)) {
|
||||
encryptionKey = fs.readFileSync(keyPath, 'utf8').trim();
|
||||
}
|
||||
} catch (keyError) {
|
||||
console.error('Error reading encryption key:', keyError);
|
||||
}
|
||||
// Получаем ключ шифрования через унифицированную утилиту
|
||||
const encryptionUtils = require('../utils/encryptionUtils');
|
||||
const encryptionKey = encryptionUtils.getEncryptionKey();
|
||||
|
||||
try {
|
||||
const contacts = req.body;
|
||||
|
||||
Reference in New Issue
Block a user