ваше сообщение коммита

This commit is contained in:
2025-06-19 20:19:09 +03:00
parent 90a088e021
commit f728c5f5da
24 changed files with 512 additions and 183 deletions

View File

@@ -141,6 +141,18 @@ app.use(async (req, res, next) => {
next();
});
// Middleware для подстановки req.user из сессии
app.use((req, res, next) => {
if (req.session && req.session.userId) {
req.user = {
id: req.session.userId,
isAdmin: req.session.isAdmin,
address: req.session.address,
};
}
next();
});
// Настройка парсеров
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

View File

@@ -34,8 +34,12 @@ function getPool() {
return pool;
}
function query(text, params) {
return pool.query(text, params);
}
function getQuery() {
return pool.query.bind(pool);
return (...args) => pool.query(...args);
}
let poolChangeCallback = null;
@@ -77,8 +81,6 @@ if (process.env.NODE_ENV !== 'migration') {
reinitPoolFromDbSettings();
}
const query = (text, params) => pool.query(text, params);
// Функция для сохранения гостевого сообщения в базе данных
async function saveGuestMessageToDatabase(message, language, guestId) {
try {
@@ -97,4 +99,4 @@ async function saveGuestMessageToDatabase(message, language, guestId) {
}
// Экспортируем функции для работы с базой данных
module.exports = { query: pool.query.bind(pool), getQuery, pool, getPool, setPoolChangeCallback };
module.exports = { query, getQuery, pool, getPool, setPoolChangeCallback };

View File

@@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS admin_read_messages (
admin_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
last_read_at TIMESTAMP NOT NULL,
PRIMARY KEY (admin_id, user_id)
);

View File

@@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS admin_read_contacts (
admin_id INTEGER NOT NULL,
contact_id INTEGER NOT NULL,
read_at TIMESTAMP NOT NULL DEFAULT NOW(),
PRIMARY KEY (admin_id, contact_id)
);

View File

@@ -1,6 +1,7 @@
const express = require('express');
const router = express.Router();
const db = require('../db');
const { broadcastMessagesUpdate } = require('../wsHub');
// GET /api/messages?userId=123
router.get('/', async (req, res) => {
@@ -28,4 +29,71 @@ 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, metadata } = req.body;
try {
const result = await db.getQuery()(
`INSERT INTO messages (user_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,NOW(),$7,$8,$9,$10,$11) RETURNING *`,
[user_id, sender_type, content, channel, role, direction, attachment_filename, attachment_mimetype, attachment_size, attachment_data, metadata]
);
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 });
}
});
module.exports = router;

View File

@@ -99,6 +99,43 @@ router.get('/', async (req, res) => {
});
*/
// Получить просмотренные контакты
router.get('/read-contacts-status', async (req, res) => {
try {
const adminId = req.user && req.user.id;
if (!adminId) {
return res.status(401).json({ error: 'Unauthorized: adminId missing' });
}
const result = await db.query(
'SELECT contact_id FROM admin_read_contacts WHERE admin_id = $1',
[adminId]
);
res.json(result.rows.map(r => r.contact_id));
} catch (e) {
console.error('[ERROR] /read-contacts-status:', e);
res.status(500).json({ error: e.message });
}
});
// Пометить контакт как просмотренный
router.post('/mark-contact-read', async (req, res) => {
try {
const adminId = req.user && req.user.id;
const { contactId } = req.body;
if (!adminId || !contactId) {
return res.status(400).json({ error: 'adminId and contactId required' });
}
await db.query(
'INSERT INTO admin_read_contacts (admin_id, contact_id, read_at) VALUES ($1, $2, NOW()) ON CONFLICT (admin_id, contact_id) DO UPDATE SET read_at = NOW()',
[adminId, contactId]
);
res.json({ success: true });
} catch (e) {
console.error('[ERROR] /mark-contact-read:', e);
res.status(500).json({ error: e.message });
}
});
// PATCH /api/users/:id — обновить имя и язык
router.patch('/:id', async (req, res) => {
const userId = req.params.id;
@@ -180,4 +217,20 @@ router.get('/:id', async (req, res, next) => {
}
});
// POST /api/users
router.post('/', async (req, res) => {
const { first_name, last_name, preferred_language } = req.body;
try {
const result = await db.getQuery()(
`INSERT INTO users (first_name, last_name, preferred_language, created_at)
VALUES ($1, $2, $3, NOW()) RETURNING *`,
[first_name, last_name, JSON.stringify(preferred_language || [])]
);
broadcastContactsUpdate();
res.json({ success: true, user: result.rows[0] });
} catch (e) {
res.status(500).json({ error: 'DB error', details: e.message });
}
});
module.exports = router;

View File

@@ -9,6 +9,7 @@ const authTokenService = require('./authTokenService');
const rpcProviderService = require('./rpcProviderService');
const { getLinkedWallet } = require('./wallet-service');
const { checkAdminRole } = require('./admin-role');
const { broadcastContactsUpdate } = require('../wsHub');
const ERC20_ABI = ['function balanceOf(address owner) view returns (uint256)'];
@@ -102,6 +103,8 @@ class AuthService {
);
}
broadcastContactsUpdate();
return { userId, isAdmin };
} catch (error) {
logger.error('Error finding or creating user:', error);
@@ -743,6 +746,8 @@ class AuthService {
delete session.tempUserId;
delete session.pendingEmail;
broadcastContactsUpdate();
return {
userId,
email: normalizedEmail,

View File

@@ -5,6 +5,7 @@ const EmailBotService = require('./emailBot.js');
const db = require('../db');
const authService = require('./auth-service');
const { checkAdminRole } = require('./admin-role');
const { broadcastContactsUpdate } = require('../wsHub');
class EmailAuth {
constructor() {
@@ -65,6 +66,9 @@ class EmailAuth {
`Generated verification code for Email auth for ${email} and sent to user's email`
);
// После каждого успешного создания пользователя:
broadcastContactsUpdate();
return { success: true, verificationCode };
} catch (error) {
logger.error('Error in email auth initialization:', error);
@@ -201,6 +205,9 @@ class EmailAuth {
delete session.tempUserId;
}
// После каждого успешного создания пользователя:
broadcastContactsUpdate();
return {
verified: true,
userId: finalUserId,

View File

@@ -8,6 +8,7 @@ const { inspect } = require('util');
const logger = require('../utils/logger');
const identityService = require('./identity-service');
const aiAssistant = require('./ai-assistant');
const { broadcastContactsUpdate } = require('../wsHub');
class EmailBotService {
constructor() {
@@ -172,6 +173,8 @@ class EmailBotService {
);
// 5. Отправить ответ на email
await this.sendEmail(fromEmail, 'Re: ' + subject, aiResponse);
// После каждого успешного создания пользователя:
broadcastContactsUpdate();
} catch (processErr) {
logger.error('Error processing incoming email:', processErr);
}

View File

@@ -544,6 +544,7 @@ class IdentityService {
await this.saveIdentity(userId, provider, providerId, true);
user = { id: userId, role: 'user' };
isNew = true;
logger.info('[WS] broadcastContactsUpdate after new user created');
broadcastContactsUpdate();
}
// Проверяем связь с кошельком

View File

@@ -7,6 +7,7 @@ const crypto = require('crypto');
const identityService = require('./identity-service');
const aiAssistant = require('./ai-assistant');
const { checkAdminRole } = require('./admin-role');
const { broadcastContactsUpdate } = require('../wsHub');
let botInstance = null;
let telegramSettingsCache = null;
@@ -252,6 +253,9 @@ async function getBot() {
} catch (error) {
logger.warn('Could not delete code message:', error);
}
// После каждого успешного создания пользователя:
broadcastContactsUpdate();
} catch (error) {
logger.error('Error in Telegram auth:', error);
await ctx.reply('Произошла ошибка при аутентификации. Попробуйте позже.');

View File

@@ -19,4 +19,12 @@ function broadcastContactsUpdate() {
}
}
module.exports = { initWSS, broadcastContactsUpdate };
function broadcastMessagesUpdate() {
for (const ws of wsClients) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'messages-updated' }));
}
}
}
module.exports = { initWSS, broadcastContactsUpdate, broadcastMessagesUpdate };