Files
DLE/backend/routes/messages.js

308 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 { isUserBlocked } = require('../utils/userUtils');
// GET /api/messages?userId=123
router.get('/', async (req, res) => {
const userId = req.query.userId;
const conversationId = req.query.conversationId;
try {
let result;
if (conversationId) {
result = await db.getQuery()(
`SELECT id, user_id, sender_type, content, channel, role, direction, created_at, attachment_filename, attachment_mimetype, attachment_size, attachment_data, metadata
FROM messages
WHERE conversation_id = $1
ORDER BY created_at ASC`,
[conversationId]
);
} else if (userId) {
result = await db.getQuery()(
`SELECT id, user_id, sender_type, content, channel, role, direction, created_at, attachment_filename, attachment_mimetype, attachment_size, attachment_data, metadata
FROM messages
WHERE user_id = $1
ORDER BY created_at ASC`,
[userId]
);
} else {
result = await db.getQuery()(
`SELECT id, user_id, sender_type, content, channel, role, direction, created_at, attachment_filename, attachment_mimetype, attachment_size, attachment_data, metadata
FROM messages
ORDER BY created_at ASC`
);
}
res.json(result.rows);
} catch (e) {
res.status(500).json({ error: 'DB error', details: e.message });
}
});
// 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, metadata } = req.body;
try {
// Проверка блокировки пользователя
if (await isUserBlocked(user_id)) {
return res.status(403).json({ error: 'Пользователь заблокирован. Сообщение не принимается.' });
}
// Проверка наличия идентификатора для выбранного канала
if (channel === 'email') {
const emailIdentity = await db.getQuery()(
'SELECT provider_id FROM user_identities WHERE user_id = $1 AND provider = $2 LIMIT 1',
[user_id, 'email']
);
if (emailIdentity.rows.length === 0) {
return res.status(400).json({ error: 'У пользователя не указан email. Сообщение не отправлено.' });
}
}
if (channel === 'telegram') {
const tgIdentity = await db.getQuery()(
'SELECT provider_id FROM user_identities WHERE user_id = $1 AND provider = $2 LIMIT 1',
[user_id, 'telegram']
);
if (tgIdentity.rows.length === 0) {
return res.status(400).json({ error: 'У пользователя не привязан Telegram. Сообщение не отправлено.' });
}
}
if (channel === 'wallet' || channel === 'web3' || channel === 'web') {
const walletIdentity = await db.getQuery()(
'SELECT provider_id FROM user_identities WHERE user_id = $1 AND provider = $2 LIMIT 1',
[user_id, 'wallet']
);
if (walletIdentity.rows.length === 0) {
return res.status(400).json({ error: 'У пользователя не привязан кошелёк. Сообщение не отправлено.' });
}
}
// 1. Проверяем, есть ли беседа для user_id
let conversationResult = await db.getQuery()(
'SELECT * FROM conversations WHERE user_id = $1 ORDER BY updated_at DESC, created_at DESC LIMIT 1',
[user_id]
);
let conversation;
if (conversationResult.rows.length === 0) {
// 2. Если нет — создаём новую беседу
const title = `Чат с пользователем ${user_id}`;
const newConv = await db.getQuery()(
'INSERT INTO conversations (user_id, title, created_at, updated_at) VALUES ($1, $2, NOW(), NOW()) RETURNING *',
[user_id, title]
);
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, content, channel, role, direction, created_at, attachment_filename, attachment_mimetype, attachment_size, attachment_data, metadata)
VALUES ($1,$2,$3,$4,$5,$6,$7,NOW(),$8,$9,$10,$11,$12) RETURNING *`,
[user_id, conversation.id, sender_type, content, channel, role, direction, attachment_filename, attachment_mimetype, attachment_size, attachment_data, metadata]
);
// 4. Если это исходящее сообщение для Telegram — отправляем через бота
if (channel === 'telegram' && direction === 'out') {
try {
console.log(`[messages.js] Попытка отправки сообщения в Telegram для user_id=${user_id}`);
// Получаем Telegram ID пользователя
const tgIdentity = await db.getQuery()(
'SELECT provider_id FROM user_identities WHERE user_id = $1 AND provider = $2 LIMIT 1',
[user_id, 'telegram']
);
console.log(`[messages.js] Результат поиска Telegram ID:`, tgIdentity.rows);
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);
} catch (sendErr) {
console.error(`[messages.js] Ошибка при отправке в Telegram:`, sendErr);
}
} else {
console.warn(`[messages.js] Не найден Telegram ID для user_id=${user_id}`);
}
} catch (err) {
console.error('[messages.js] Ошибка отправки сообщения в Telegram:', err);
}
}
// 5. Если это исходящее сообщение для Email — отправляем email
if (channel === 'email' && direction === 'out') {
try {
// Получаем email пользователя
const emailIdentity = await db.getQuery()(
'SELECT provider_id FROM user_identities WHERE user_id = $1 AND provider = $2 LIMIT 1',
[user_id, 'email']
);
if (emailIdentity.rows.length > 0) {
const email = emailIdentity.rows[0].provider_id;
await emailBot.sendEmail(email, 'Новое сообщение', content);
}
} catch (err) {
console.error('[messages.js] Ошибка отправки email:', err);
}
}
broadcastMessagesUpdate();
res.json({ success: true, message: result.rows[0] });
} catch (e) {
res.status(500).json({ error: 'DB error', details: e.message });
}
});
// POST /api/messages/mark-read
router.post('/mark-read', async (req, res) => {
try {
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;
if (!adminId) {
console.error('[ERROR] /mark-read: adminId (req.user.id) is missing');
return res.status(401).json({ error: 'Unauthorized: adminId missing' });
}
if (!userId || !lastReadAt) {
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 });
} catch (e) {
console.error('[ERROR] /mark-read:', e);
res.status(500).json({ error: e.message });
}
});
// GET /api/messages/read-status
router.get('/read-status', async (req, res) => {
try {
console.log('[DEBUG] /read-status req.user:', req.user);
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;
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]);
console.log('[DEBUG] /read-status SQL result:', result.rows);
const map = {};
for (const row of result.rows) {
map[row.user_id] = row.last_read_at;
}
res.json(map);
} catch (e) {
console.error('[ERROR] /read-status:', e);
res.status(500).json({ error: e.message });
}
});
// GET /api/conversations?userId=123
router.get('/conversations', async (req, res) => {
const userId = req.query.userId;
if (!userId) return res.status(400).json({ error: 'userId required' });
try {
const result = await db.getQuery()(
'SELECT * FROM conversations WHERE user_id = $1 ORDER BY updated_at DESC, created_at DESC LIMIT 1',
[userId]
);
if (result.rows.length === 0) {
return res.status(404).json({ error: 'Conversation not found' });
}
res.json(result.rows[0]);
} catch (e) {
res.status(500).json({ error: 'DB error', details: e.message });
}
});
// Массовая рассылка сообщения во все каналы пользователя
router.post('/broadcast', async (req, res) => {
const { user_id, content } = req.body;
if (!user_id || !content) {
return res.status(400).json({ error: 'user_id и content обязательны' });
}
try {
// Получаем все идентификаторы пользователя
const identitiesRes = await db.getQuery()(
'SELECT provider, provider_id FROM user_identities WHERE user_id = $1',
[user_id]
);
const identities = identitiesRes.rows;
// --- Найти или создать беседу (conversation) ---
let conversationResult = await db.getQuery()(
'SELECT * FROM conversations WHERE user_id = $1 ORDER BY updated_at DESC, created_at DESC LIMIT 1',
[user_id]
);
let conversation;
if (conversationResult.rows.length === 0) {
const title = `Чат с пользователем ${user_id}`;
const newConv = await db.getQuery()(
'INSERT INTO conversations (user_id, title, created_at, updated_at) VALUES ($1, $2, NOW(), NOW()) RETURNING *',
[user_id, title]
);
conversation = newConv.rows[0];
} else {
conversation = conversationResult.rows[0];
}
const results = [];
let sent = false;
// Email
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, content, channel, role, direction, created_at, metadata)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), $8)`,
[user_id, conversation.id, 'admin', content, 'email', 'user', 'out', JSON.stringify({ broadcast: true })]
);
results.push({ channel: 'email', status: 'sent' });
sent = true;
} catch (err) {
results.push({ channel: 'email', status: 'error', error: err.message });
}
}
// Telegram
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, content, channel, role, direction, created_at, metadata)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), $8)`,
[user_id, conversation.id, 'admin', content, 'telegram', 'user', 'out', JSON.stringify({ broadcast: true })]
);
results.push({ channel: 'telegram', status: 'sent' });
sent = true;
} catch (err) {
results.push({ channel: 'telegram', status: 'error', error: err.message });
}
}
// Wallet/web3
const wallet = identities.find(i => i.provider === 'wallet')?.provider_id;
if (wallet) {
// Здесь можно реализовать отправку через web3, если нужно
await db.getQuery()(
`INSERT INTO messages (user_id, conversation_id, sender_type, content, channel, role, direction, created_at, metadata)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), $8)`,
[user_id, conversation.id, 'admin', content, 'wallet', 'user', 'out', JSON.stringify({ broadcast: true })]
);
results.push({ channel: 'wallet', status: 'saved' });
sent = true;
}
if (!sent) {
return res.status(400).json({ error: 'У пользователя нет ни одного канала для рассылки.' });
}
res.json({ success: true, results });
} catch (e) {
res.status(500).json({ error: 'Broadcast error', details: e.message });
}
});
module.exports = router;