ваше сообщение коммита
This commit is contained in:
@@ -932,10 +932,10 @@ router.get('/history', requireAuth, async (req, res) => {
|
||||
try {
|
||||
// Если нужен только подсчет
|
||||
if (countOnly) {
|
||||
let countQuery = 'SELECT COUNT(*) FROM messages WHERE user_id = $1';
|
||||
let countParams = [userId];
|
||||
let countQuery = 'SELECT COUNT(*) FROM messages WHERE user_id = $1 AND message_type = $2';
|
||||
let countParams = [userId, 'user_chat'];
|
||||
if (conversationId) {
|
||||
countQuery += ' AND conversation_id = $2';
|
||||
countQuery += ' AND conversation_id = $3';
|
||||
countParams.push(conversationId);
|
||||
}
|
||||
const countResult = await db.getQuery()(countQuery, countParams);
|
||||
@@ -944,7 +944,10 @@ router.get('/history', requireAuth, async (req, res) => {
|
||||
}
|
||||
|
||||
// Загружаем сообщения через encryptedDb
|
||||
const whereConditions = { user_id: userId };
|
||||
const whereConditions = {
|
||||
user_id: userId,
|
||||
message_type: 'user_chat' // Фильтруем только публичные сообщения
|
||||
};
|
||||
if (conversationId) {
|
||||
whereConditions.conversation_id = conversationId;
|
||||
}
|
||||
|
||||
@@ -41,24 +41,25 @@ router.get('/', async (req, res) => {
|
||||
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
|
||||
`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
|
||||
FROM messages
|
||||
WHERE conversation_id = $1
|
||||
WHERE conversation_id = $1 AND message_type = 'user_chat'
|
||||
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
|
||||
`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
|
||||
FROM messages
|
||||
WHERE user_id = $1
|
||||
WHERE user_id = $1 AND message_type = 'user_chat'
|
||||
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
|
||||
`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
|
||||
FROM messages
|
||||
WHERE message_type = 'user_chat'
|
||||
ORDER BY created_at ASC`,
|
||||
[encryptionKey]
|
||||
);
|
||||
@@ -72,6 +73,34 @@ 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');
|
||||
@@ -120,29 +149,72 @@ 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 (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]
|
||||
);
|
||||
conversation = newConv.rows[0];
|
||||
|
||||
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 {
|
||||
conversation = conversationResult.rows[0];
|
||||
// Для обычных пользовательских сообщений используем старую логику с 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) {
|
||||
// Создаем новую беседу
|
||||
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];
|
||||
}
|
||||
}
|
||||
// 3. Сохраняем сообщение с conversation_id
|
||||
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]
|
||||
);
|
||||
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]
|
||||
);
|
||||
}
|
||||
// 4. Если это исходящее сообщение для Telegram — отправляем через бота
|
||||
if (channel === 'telegram' && direction === 'out') {
|
||||
try {
|
||||
@@ -186,7 +258,10 @@ router.post('/', async (req, res) => {
|
||||
// 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 });
|
||||
@@ -199,7 +274,8 @@ 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 } = req.body;
|
||||
const { userId, lastReadAt, messageType = 'user_chat' } = req.body;
|
||||
|
||||
if (!adminId) {
|
||||
// console.error('[ERROR] /mark-read: adminId (req.user.id) is missing');
|
||||
return res.status(401).json({ error: 'Unauthorized: adminId missing' });
|
||||
@@ -208,12 +284,30 @@ 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' });
|
||||
}
|
||||
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 });
|
||||
|
||||
// Логика зависит от типа сообщения
|
||||
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 });
|
||||
} catch (e) {
|
||||
// console.error('[ERROR] /mark-read:', e);
|
||||
res.status(500).json({ error: e.message });
|
||||
@@ -227,11 +321,24 @@ 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' });
|
||||
}
|
||||
const result = await db.query('SELECT user_id, last_read_at FROM admin_read_messages WHERE admin_id = $1', [adminId]);
|
||||
|
||||
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"' });
|
||||
}
|
||||
|
||||
// console.log('[DEBUG] /read-status SQL result:', result.rows);
|
||||
const map = {};
|
||||
for (const row of result.rows) {
|
||||
@@ -346,9 +453,9 @@ router.post('/broadcast', async (req, res) => {
|
||||
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, 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), NOW())`,
|
||||
[user_id, conversation.id, 'admin', content, 'email', 'user', 'out', encryptionKey]
|
||||
`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;
|
||||
@@ -363,9 +470,9 @@ router.post('/broadcast', async (req, res) => {
|
||||
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, 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), NOW())`,
|
||||
[user_id, conversation.id, 'admin', content, 'telegram', 'user', 'out', encryptionKey]
|
||||
`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;
|
||||
@@ -435,4 +542,254 @@ 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;
|
||||
@@ -150,7 +150,12 @@ router.get('/', requireAuth, async (req, res, next) => {
|
||||
WHEN u.last_name_encrypted IS NULL OR u.last_name_encrypted = '' THEN NULL
|
||||
ELSE decrypt_text(u.last_name_encrypted, $${idx++})
|
||||
END as last_name,
|
||||
u.created_at, u.preferred_language, u.is_blocked,
|
||||
u.created_at, u.preferred_language, u.is_blocked, u.role,
|
||||
CASE
|
||||
WHEN u.role = 'editor' THEN 'admin'
|
||||
WHEN u.role = 'readonly' THEN 'admin'
|
||||
ELSE 'user'
|
||||
END as contact_type,
|
||||
(SELECT decrypt_text(provider_id_encrypted, $${idx++}) FROM user_identities WHERE user_id = u.id AND provider_encrypted = encrypt_text('email', $${idx++}) LIMIT 1) AS email,
|
||||
(SELECT decrypt_text(provider_id_encrypted, $${idx++}) FROM user_identities WHERE user_id = u.id AND provider_encrypted = encrypt_text('telegram', $${idx++}) LIMIT 1) AS telegram,
|
||||
(SELECT decrypt_text(provider_id_encrypted, $${idx++}) FROM user_identities WHERE user_id = u.id AND provider_encrypted = encrypt_text('wallet', $${idx++}) LIMIT 1) AS wallet
|
||||
@@ -219,7 +224,9 @@ router.get('/', requireAuth, async (req, res, next) => {
|
||||
wallet: u.wallet || null,
|
||||
created_at: u.created_at,
|
||||
preferred_language: u.preferred_language || [],
|
||||
is_blocked: u.is_blocked || false
|
||||
is_blocked: u.is_blocked || false,
|
||||
contact_type: u.contact_type || 'user',
|
||||
role: u.role || 'user'
|
||||
}));
|
||||
|
||||
res.json({ success: true, contacts });
|
||||
|
||||
Reference in New Issue
Block a user