diff --git a/backend/db/migrations/025_add_direction_to_messages.sql b/backend/db/migrations/025_add_direction_to_messages.sql
new file mode 100644
index 0000000..972dd6d
--- /dev/null
+++ b/backend/db/migrations/025_add_direction_to_messages.sql
@@ -0,0 +1,2 @@
+-- Миграция: добавление поля direction в таблицу messages
+ALTER TABLE messages ADD COLUMN IF NOT EXISTS direction VARCHAR(8);
\ No newline at end of file
diff --git a/backend/routes/users.js b/backend/routes/users.js
index d43e11d..2139a04 100644
--- a/backend/routes/users.js
+++ b/backend/routes/users.js
@@ -6,9 +6,9 @@ const { requireAuth } = require('../middleware/auth');
// const userService = require('../services/userService');
// Получение списка пользователей
-router.get('/', (req, res) => {
- res.json({ message: 'Users API endpoint' });
-});
+// router.get('/', (req, res) => {
+// res.json({ message: 'Users API endpoint' });
+// });
// Получение информации о пользователе
router.get('/:address', (req, res) => {
@@ -92,6 +92,36 @@ router.put('/profile', requireAuth, async (req, res) => {
});
*/
+// Получение списка пользователей с контактами
+router.get('/', async (req, res, next) => {
+ try {
+ const usersResult = await db.getQuery()('SELECT id, first_name, last_name, created_at FROM users ORDER BY id');
+ const users = usersResult.rows;
+ // Получаем все user_identities разом
+ const identitiesResult = await db.getQuery()('SELECT user_id, provider, provider_id FROM user_identities');
+ const identities = identitiesResult.rows;
+ // Группируем идентификаторы по user_id
+ const identityMap = {};
+ for (const id of identities) {
+ if (!identityMap[id.user_id]) identityMap[id.user_id] = {};
+ if (!identityMap[id.user_id][id.provider]) identityMap[id.user_id][id.provider] = id.provider_id;
+ }
+ // Собираем контакты
+ const contacts = users.map(u => ({
+ id: u.id,
+ name: [u.first_name, u.last_name].filter(Boolean).join(' ') || null,
+ email: identityMap[u.id]?.email || null,
+ telegram: identityMap[u.id]?.telegram || null,
+ wallet: identityMap[u.id]?.wallet || null,
+ created_at: u.created_at
+ }));
+ res.json({ success: true, contacts });
+ } catch (error) {
+ logger.error('Error fetching contacts:', error);
+ next(error);
+ }
+});
+
// GET /api/users - Получить список всех пользователей (пример, может требовать прав администратора)
// В текущей реализации этот маршрут не используется и закомментирован
/*
diff --git a/backend/server.js b/backend/server.js
index 10eaf2d..3d3e0fe 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -2,7 +2,6 @@ require('dotenv').config();
const express = require('express');
const cors = require('cors');
const { ethers } = require('ethers');
-const emailBot = require('./services/emailBot');
const session = require('express-session');
const { app, nonceStore } = require('./app');
const usersRouter = require('./routes/users');
@@ -15,6 +14,7 @@ const { getBot, stopBot } = require('./services/telegramBot');
const pgSession = require('connect-pg-simple')(session);
const authService = require('./services/auth-service');
const logger = require('./utils/logger');
+const EmailBotService = require('./services/emailBot.js');
const PORT = process.env.PORT || 8000;
@@ -28,12 +28,20 @@ async function initServices() {
console.log('Инициализация сервисов...');
// Останавливаем предыдущий экземпляр бота
+ console.log('Перед stopBot');
await stopBot();
+ console.log('После stopBot, перед getBot');
+ getBot();
+ console.log('После getBot, перед созданием EmailBotService');
// Добавляем обработку ошибок при запуске бота
try {
- await getBot(); // getBot теперь асинхронный и сам запускает бота
- console.log('Telegram bot started');
+ console.log('Пробуем создать экземпляр EmailBotService');
+
+ // Запуск email-бота
+ console.log('Создаём экземпляр EmailBotService');
+ // const emailBot = new EmailBotService();
+ // await emailBot.start();
// Добавляем graceful shutdown
process.once('SIGINT', async () => {
@@ -53,6 +61,7 @@ async function initServices() {
// Бот будет запущен при следующем перезапуске
} else {
logger.error('Error launching Telegram bot:', error);
+ console.error('Ошибка при запуске Telegram-бота:', error);
}
}
@@ -91,12 +100,13 @@ app.get('/api/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
-// Запуск сервера
-const host = app.get('host');
-app.listen(PORT, host, async () => {
+// Для отладки
+// const host = app.get('host');
+// console.log('host:', host);
+app.listen(PORT, async () => {
try {
await initServices();
- console.log(`Server is running on http://${host}:${PORT}`);
+ console.log(`Server is running on port ${PORT}`);
} catch (error) {
console.error('Error starting server:', error);
process.exit(1);
diff --git a/backend/services/auth-service.js b/backend/services/auth-service.js
index e894393..1c5dbb5 100644
--- a/backend/services/auth-service.js
+++ b/backend/services/auth-service.js
@@ -7,6 +7,7 @@ const verificationService = require('./verification-service'); // Использ
const identityService = require('./identity-service'); // <-- ДОБАВЛЕН ИМПОРТ
const authTokenService = require('./authTokenService');
const rpcProviderService = require('./rpcProviderService');
+const { getLinkedWallet } = require('./wallet-service');
const ERC20_ABI = ['function balanceOf(address owner) view returns (uint256)'];
@@ -359,26 +360,6 @@ class AuthService {
}
}
- // Получение связанного кошелька
- async getLinkedWallet(userId) {
- logger.info(`[getLinkedWallet] Called with userId: ${userId} (Type: ${typeof userId})`);
- try {
- const result = await db.getQuery()(
- `SELECT provider_id as address
- FROM user_identities
- WHERE user_id = $1 AND provider = 'wallet'`,
- [userId]
- );
- logger.info(`[getLinkedWallet] DB query result for userId ${userId}:`, result.rows);
- const address = result.rows[0]?.address;
- logger.info(`[getLinkedWallet] Returning address: ${address} for userId ${userId}`);
- return address;
- } catch (error) {
- logger.error(`[getLinkedWallet] Error fetching linked wallet for userId ${userId}:`, error);
- return undefined;
- }
- }
-
/**
* Проверяет роль пользователя Telegram
* @param {number} userId - ID пользователя
@@ -387,7 +368,7 @@ class AuthService {
async checkUserRole(userId) {
try {
// Проверяем наличие связанного кошелька
- const wallet = await this.getLinkedWallet(userId);
+ const wallet = await getLinkedWallet(userId);
// Если кошелек не привязан, пользователь получает роль user
// с базовым доступом к чату и истории сообщений
@@ -429,7 +410,7 @@ class AuthService {
}
// Проверяем наличие кошелька и определяем роль
- const wallet = await this.getLinkedWallet(userId);
+ const wallet = await getLinkedWallet(userId);
let role = 'user'; // Базовая роль для доступа к чату
if (wallet) {
@@ -814,7 +795,7 @@ class AuthService {
// 4. Проверить роль на основе привязанного кошелька
try {
- const linkedWallet = await this.getLinkedWallet(userId);
+ const linkedWallet = await getLinkedWallet(userId);
if (linkedWallet && linkedWallet.provider_id) {
logger.info(`[handleEmailVerification] Found linked wallet ${linkedWallet.provider_id}. Checking role...`);
const isAdmin = await this.checkAdminRole(linkedWallet.provider_id);
diff --git a/backend/services/emailAuth.js b/backend/services/emailAuth.js
index 0e5a3a4..57b689a 100644
--- a/backend/services/emailAuth.js
+++ b/backend/services/emailAuth.js
@@ -1,7 +1,7 @@
const { pool } = require('../db');
const verificationService = require('./verification-service');
const logger = require('../utils/logger');
-const EmailBotService = require('./emailBot');
+const EmailBotService = require('./emailBot.js');
const db = require('../db');
const authService = require('./auth-service');
diff --git a/backend/services/emailBot.js b/backend/services/emailBot.js
index 8638bcd..7ab25c6 100644
--- a/backend/services/emailBot.js
+++ b/backend/services/emailBot.js
@@ -5,6 +5,8 @@ const simpleParser = require('mailparser').simpleParser;
const { processMessage } = require('./ai-assistant');
const { inspect } = require('util');
const logger = require('../utils/logger');
+const identityService = require('./identity-service');
+const aiAssistant = require('./ai-assistant');
class EmailBotService {
async getSettingsFromDb() {
@@ -124,6 +126,49 @@ class EmailBotService {
logger.error(`Error parsing message: ${err}`);
return;
}
+ try {
+ const fromEmail = parsed.from?.value?.[0]?.address;
+ const subject = parsed.subject || '';
+ const text = parsed.text || '';
+ const html = parsed.html || '';
+ // 1. Найти или создать пользователя
+ const { userId, role } = await identityService.findOrCreateUserWithRole('email', fromEmail);
+ // 2. Сохранить письмо и вложения в messages
+ let hasAttachments = parsed.attachments && parsed.attachments.length > 0;
+ if (hasAttachments) {
+ for (const att of parsed.attachments) {
+ 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)`,
+ [userId, 'user', text, 'email', role, 'in',
+ att.filename,
+ att.contentType,
+ att.size,
+ att.content,
+ JSON.stringify({ subject, html })
+ ]
+ );
+ }
+ } else {
+ await db.getQuery()(
+ `INSERT INTO messages (user_id, sender_type, content, channel, role, direction, created_at, metadata)
+ VALUES ($1, $2, $3, $4, $5, $6, NOW(), $7)`,
+ [userId, 'user', text, 'email', role, 'in', JSON.stringify({ subject, html })]
+ );
+ }
+ // 3. Получить ответ от ИИ
+ const aiResponse = await aiAssistant.getResponse(text, 'auto');
+ // 4. Сохранить ответ в БД
+ await db.getQuery()(
+ `INSERT INTO messages (user_id, sender_type, content, channel, role, direction, created_at, metadata)
+ VALUES ($1, $2, $3, $4, $5, $6, NOW(), $7)`,
+ [userId, 'assistant', aiResponse, 'email', role, 'out', JSON.stringify({ subject, html })]
+ );
+ // 5. Отправить ответ на email
+ await this.sendEmail(fromEmail, 'Re: ' + subject, aiResponse);
+ } catch (processErr) {
+ logger.error('Error processing incoming email:', processErr);
+ }
});
});
});
@@ -181,6 +226,45 @@ class EmailBotService {
throw error;
}
}
+
+ async start() {
+ logger.info('[EmailBot] start() called');
+ const imapConfig = await this.getImapConfig();
+ // Логируем IMAP-конфиг (без пароля)
+ const safeConfig = { ...imapConfig };
+ if (safeConfig.password) safeConfig.password = '***';
+ logger.info('[EmailBot] IMAP config:', safeConfig);
+ let attempt = 0;
+ const maxAttempts = 3;
+ const tryConnect = () => {
+ attempt++;
+ logger.info(`[EmailBot] IMAP connect attempt ${attempt}`);
+ this.imap = new Imap(imapConfig);
+ this.imap.once('ready', () => {
+ logger.info('[EmailBot] IMAP connection ready');
+ this.imap.openBox('INBOX', false, (err, box) => {
+ if (err) {
+ logger.error(`[EmailBot] Error opening INBOX: ${err.message}`);
+ this.imap.end();
+ return;
+ }
+ logger.info('[EmailBot] INBOX opened successfully');
+ });
+ // После успешного подключения — обычная логика
+ this.checkEmails();
+ logger.info('[EmailBot] Email bot started and IMAP connection initiated');
+ });
+ this.imap.once('error', (err) => {
+ logger.error(`[EmailBot] IMAP connection error: ${err.message}`);
+ if (err.message && err.message.toLowerCase().includes('timed out') && attempt < maxAttempts) {
+ logger.warn(`[EmailBot] IMAP reconnecting in 10 seconds (attempt ${attempt + 1})...`);
+ setTimeout(tryConnect, 10000);
+ }
+ });
+ this.imap.connect();
+ };
+ tryConnect();
+ }
}
module.exports = EmailBotService;
diff --git a/backend/services/identity-service.js b/backend/services/identity-service.js
index c9b1413..00d1603 100644
--- a/backend/services/identity-service.js
+++ b/backend/services/identity-service.js
@@ -1,5 +1,6 @@
const db = require('../db');
const logger = require('../utils/logger');
+const { getLinkedWallet } = require('./wallet-service');
/**
* Сервис для работы с идентификаторами пользователей
@@ -521,6 +522,38 @@ class IdentityService {
return { success: false, error: error.message };
}
}
+
+ /**
+ * Универсальная функция: найти или создать пользователя по идентификатору, привязать идентификатор, проверить роль
+ * @param {string} provider - Тип идентификатора ('email' | 'telegram')
+ * @param {string} providerId - Значение идентификатора
+ * @param {object} [options] - Дополнительные опции
+ * @returns {Promise<{userId: number, role: string, isNew: boolean}>}
+ */
+ async findOrCreateUserWithRole(provider, providerId, options = {}) {
+ let user = await this.findUserByIdentity(provider, providerId);
+ let isNew = false;
+ if (!user) {
+ // Создаем пользователя
+ const newUserResult = await db.getQuery()('INSERT INTO users (role) VALUES ($1) RETURNING id', ['user']);
+ const userId = newUserResult.rows[0].id;
+ await this.saveIdentity(userId, provider, providerId, true);
+ user = { id: userId, role: 'user' };
+ isNew = true;
+ }
+ // Проверяем связь с кошельком
+ const wallet = await getLinkedWallet(user.id);
+ let role = 'user';
+ if (wallet) {
+ const isAdmin = await authService.checkAdminRole(wallet);
+ role = isAdmin ? 'admin' : 'user';
+ // Обновляем роль в users, если изменилась
+ if (user.role !== role) {
+ await db.getQuery()('UPDATE users SET role = $1 WHERE id = $2', [role, user.id]);
+ }
+ }
+ return { userId: user.id, role, isNew };
+ }
}
module.exports = new IdentityService();
diff --git a/backend/services/telegramBot.js b/backend/services/telegramBot.js
index 82a6c84..2138aaa 100644
--- a/backend/services/telegramBot.js
+++ b/backend/services/telegramBot.js
@@ -4,6 +4,8 @@ const db = require('../db');
const authService = require('./auth-service');
const verificationService = require('./verification-service');
const crypto = require('crypto');
+const identityService = require('./identity-service');
+const aiAssistant = require('./ai-assistant');
let botInstance = null;
let telegramSettingsCache = null;
@@ -27,227 +29,305 @@ async function getBot() {
ctx.reply('Добро пожаловать! Отправьте код подтверждения для аутентификации.');
});
- // Обработка кодов верификации
+ // Универсальный обработчик текстовых сообщений
botInstance.on('text', async (ctx) => {
- const code = ctx.message.text.trim();
-
- try {
- // Получаем код верификации для всех активных кодов с провайдером telegram
- const codeResult = await db.getQuery()(
- `SELECT * FROM verification_codes
- WHERE code = $1
- AND provider = 'telegram'
- AND used = false
- AND expires_at > NOW()`,
- [code]
- );
-
- if (codeResult.rows.length === 0) {
- ctx.reply('Неверный код подтверждения');
- return;
- }
-
- const verification = codeResult.rows[0];
- const providerId = verification.provider_id;
- const linkedUserId = verification.user_id; // Получаем связанный userId если он есть
- let userId;
- let userRole = 'user'; // Роль по умолчанию
-
- // Отмечаем код как использованный
- await db.getQuery()('UPDATE verification_codes SET used = true WHERE id = $1', [
- verification.id,
- ]);
-
- logger.info('Starting Telegram auth process for code:', code);
-
- // Проверяем, существует ли уже пользователь с таким Telegram ID
- const existingTelegramUser = await db.getQuery()(
- `SELECT ui.user_id
- FROM user_identities ui
- WHERE ui.provider = 'telegram' AND ui.provider_id = $1`,
- [ctx.from.id.toString()]
- );
-
- if (existingTelegramUser.rows.length > 0) {
- // Если пользователь с таким Telegram ID уже существует, используем его
- userId = existingTelegramUser.rows[0].user_id;
- logger.info(`Using existing user ${userId} for Telegram account ${ctx.from.id}`);
- } else {
- // Если код верификации был связан с существующим пользователем, используем его
- if (linkedUserId) {
- // Используем userId из кода верификации
- userId = linkedUserId;
- // Связываем Telegram с этим пользователем
- await db.getQuery()(
- `INSERT INTO user_identities
- (user_id, provider, provider_id, created_at)
- VALUES ($1, $2, $3, NOW())`,
- [userId, 'telegram', ctx.from.id.toString()]
- );
- logger.info(
- `Linked Telegram account ${ctx.from.id} to pre-authenticated user ${userId}`
- );
- } else {
- // Проверяем, есть ли пользователь, связанный с гостевым идентификатором
- let existingUserWithGuestId = null;
- if (providerId) {
- const guestUserResult = await db.getQuery()(
- `SELECT user_id FROM guest_user_mapping WHERE guest_id = $1`,
- [providerId]
- );
- if (guestUserResult.rows.length > 0) {
- existingUserWithGuestId = guestUserResult.rows[0].user_id;
- logger.info(
- `Found existing user ${existingUserWithGuestId} by guest ID ${providerId}`
- );
- }
- }
-
- if (existingUserWithGuestId) {
- // Используем существующего пользователя и добавляем ему Telegram идентификатор
- userId = existingUserWithGuestId;
- await db.getQuery()(
- `INSERT INTO user_identities
- (user_id, provider, provider_id, created_at)
- VALUES ($1, $2, $3, NOW())`,
- [userId, 'telegram', ctx.from.id.toString()]
- );
- logger.info(`Linked Telegram account ${ctx.from.id} to existing user ${userId}`);
- } else {
- // Создаем нового пользователя, если не нашли существующего
- const userResult = await db.getQuery()(
- 'INSERT INTO users (created_at, role) VALUES (NOW(), $1) RETURNING id',
- ['user']
- );
- userId = userResult.rows[0].id;
-
- // Связываем Telegram с новым пользователем
- await db.getQuery()(
- `INSERT INTO user_identities
- (user_id, provider, provider_id, created_at)
- VALUES ($1, $2, $3, NOW())`,
- [userId, 'telegram', ctx.from.id.toString()]
- );
-
- // Если был гостевой ID, связываем его с новым пользователем
- if (providerId) {
- await db.getQuery()(
- `INSERT INTO guest_user_mapping
- (user_id, guest_id)
- VALUES ($1, $2)
- ON CONFLICT (guest_id) DO UPDATE SET user_id = $1`,
- [userId, providerId]
- );
- }
-
- logger.info(`Created new user ${userId} with Telegram account ${ctx.from.id}`);
- }
- }
- }
-
- // ----> НАЧАЛО: Проверка роли на основе привязанного кошелька <----
- if (userId) { // Убедимся, что userId определен
- logger.info(`[TelegramBot] Checking linked wallet for determined userId: ${userId} (Type: ${typeof userId})`);
- try {
- const linkedWallet = await authService.getLinkedWallet(userId);
- if (linkedWallet) {
- logger.info(`[TelegramBot] Found linked wallet ${linkedWallet} for user ${userId}. Checking role...`);
- const isAdmin = await authService.checkAdminRole(linkedWallet);
- userRole = isAdmin ? 'admin' : 'user';
- logger.info(`[TelegramBot] Role for user ${userId} determined as: ${userRole}`);
-
- // Опционально: Обновить роль в таблице users
- const currentUser = await db.getQuery()('SELECT role FROM users WHERE id = $1', [userId]);
- if (currentUser.rows.length > 0 && currentUser.rows[0].role !== userRole) {
- await db.getQuery()('UPDATE users SET role = $1 WHERE id = $2', [userRole, userId]);
- logger.info(`[TelegramBot] Updated user role in DB to ${userRole}`);
- }
- } else {
- logger.info(`[TelegramBot] No linked wallet found for user ${userId}. Checking current DB role.`);
- // Если кошелька нет, берем текущую роль из базы
- const currentUser = await db.getQuery()('SELECT role FROM users WHERE id = $1', [userId]);
- if (currentUser.rows.length > 0) {
- userRole = currentUser.rows[0].role;
- }
- }
- } catch (roleCheckError) {
- logger.error(`[TelegramBot] Error checking admin role for user ${userId}:`, roleCheckError);
- // В случае ошибки берем роль из базы или оставляем 'user'
- try {
- const currentUser = await db.getQuery()('SELECT role FROM users WHERE id = $1', [userId]);
- if (currentUser.rows.length > 0) { userRole = currentUser.rows[0].role; }
- } catch (dbError) { /* ignore */ }
- }
- } else {
- logger.error('[TelegramBot] Cannot check role because userId is undefined!');
- }
- // ----> КОНЕЦ: Проверка роли <----
-
- // Логируем userId перед обновлением сессии
- logger.info(`[telegramBot] Attempting to update session for userId: ${userId}`);
-
- // Находим последнюю активную сессию для данного userId
- let activeSessionId = null;
+ const text = ctx.message.text.trim();
+ // 1. Если команда — пропустить
+ if (text.startsWith('/')) return;
+ // 2. Проверка: это потенциальный код?
+ const isPotentialCode = (str) => /^[A-Z0-9]{6}$/i.test(str);
+ if (isPotentialCode(text)) {
try {
- // Ищем сессию, где есть userId и она не истекла (проверка expires_at)
- // Сортируем по expires_at DESC чтобы взять самую "свежую", если их несколько
- const sessionResult = await db.getQuery()(
- `SELECT sid FROM session
- WHERE sess ->> 'userId' = $1
- AND expire > NOW()
- ORDER BY expire DESC
- LIMIT 1`,
- [userId?.toString()] // Используем optional chaining и преобразуем в строку
+ // Получаем код верификации для всех активных кодов с провайдером telegram
+ const codeResult = await db.getQuery()(
+ `SELECT * FROM verification_codes
+ WHERE code = $1
+ AND provider = 'telegram'
+ AND used = false
+ AND expires_at > NOW()`,
+ [text.toUpperCase()]
);
-
- if (sessionResult.rows.length > 0) {
- activeSessionId = sessionResult.rows[0].sid;
- logger.info(`[telegramBot] Found active session ID ${activeSessionId} for user ${userId}`);
- // Обновляем найденную сессию в базе данных, добавляя/перезаписывая данные Telegram
- const updateResult = await db.getQuery()(
- `UPDATE session
- SET sess = (sess::jsonb || $1::jsonb)::json
- WHERE sid = $2`,
- [
- JSON.stringify({
- // authenticated: true, // Не перезаписываем, т.к. сессия уже должна быть аутентифицирована
- authType: 'telegram', // Обновляем тип аутентификации
- telegramId: ctx.from.id.toString(),
- telegramUsername: ctx.from.username,
- telegramFirstName: ctx.from.first_name,
- role: userRole, // Записываем определенную роль
- // userId: userId?.toString() // userId уже должен быть в сессии
- }),
- activeSessionId // Обновляем по найденному session ID
- ]
+ if (codeResult.rows.length === 0) {
+ ctx.reply('Неверный код подтверждения');
+ return;
+ }
+
+ const verification = codeResult.rows[0];
+ const providerId = verification.provider_id;
+ const linkedUserId = verification.user_id; // Получаем связанный userId если он есть
+ let userId;
+ let userRole = 'user'; // Роль по умолчанию
+
+ // Отмечаем код как использованный
+ await db.getQuery()('UPDATE verification_codes SET used = true WHERE id = $1', [
+ verification.id,
+ ]);
+
+ logger.info('Starting Telegram auth process for code:', text);
+
+ // Проверяем, существует ли уже пользователь с таким Telegram ID
+ const existingTelegramUser = await db.getQuery()(
+ `SELECT ui.user_id
+ FROM user_identities ui
+ WHERE ui.provider = 'telegram' AND ui.provider_id = $1`,
+ [ctx.from.id.toString()]
+ );
+
+ if (existingTelegramUser.rows.length > 0) {
+ // Если пользователь с таким Telegram ID уже существует, используем его
+ userId = existingTelegramUser.rows[0].user_id;
+ logger.info(`Using existing user ${userId} for Telegram account ${ctx.from.id}`);
+ } else {
+ // Если код верификации был связан с существующим пользователем, используем его
+ if (linkedUserId) {
+ // Используем userId из кода верификации
+ userId = linkedUserId;
+ // Связываем Telegram с этим пользователем
+ await db.getQuery()(
+ `INSERT INTO user_identities
+ (user_id, provider, provider_id, created_at)
+ VALUES ($1, $2, $3, NOW())`,
+ [userId, 'telegram', ctx.from.id.toString()]
+ );
+ logger.info(
+ `Linked Telegram account ${ctx.from.id} to pre-authenticated user ${userId}`
+ );
+ } else {
+ // Проверяем, есть ли пользователь, связанный с гостевым идентификатором
+ let existingUserWithGuestId = null;
+ if (providerId) {
+ const guestUserResult = await db.getQuery()(
+ `SELECT user_id FROM guest_user_mapping WHERE guest_id = $1`,
+ [providerId]
+ );
+ if (guestUserResult.rows.length > 0) {
+ existingUserWithGuestId = guestUserResult.rows[0].user_id;
+ logger.info(
+ `Found existing user ${existingUserWithGuestId} by guest ID ${providerId}`
+ );
+ }
+ }
+
+ if (existingUserWithGuestId) {
+ // Используем существующего пользователя и добавляем ему Telegram идентификатор
+ userId = existingUserWithGuestId;
+ await db.getQuery()(
+ `INSERT INTO user_identities
+ (user_id, provider, provider_id, created_at)
+ VALUES ($1, $2, $3, NOW())`,
+ [userId, 'telegram', ctx.from.id.toString()]
+ );
+ logger.info(`Linked Telegram account ${ctx.from.id} to existing user ${userId}`);
+ } else {
+ // Создаем нового пользователя, если не нашли существующего
+ const userResult = await db.getQuery()(
+ 'INSERT INTO users (created_at, role) VALUES (NOW(), $1) RETURNING id',
+ ['user']
+ );
+ userId = userResult.rows[0].id;
+
+ // Связываем Telegram с новым пользователем
+ await db.getQuery()(
+ `INSERT INTO user_identities
+ (user_id, provider, provider_id, created_at)
+ VALUES ($1, $2, $3, NOW())`,
+ [userId, 'telegram', ctx.from.id.toString()]
+ );
+
+ // Если был гостевой ID, связываем его с новым пользователем
+ if (providerId) {
+ await db.getQuery()(
+ `INSERT INTO guest_user_mapping
+ (user_id, guest_id)
+ VALUES ($1, $2)
+ ON CONFLICT (guest_id) DO UPDATE SET user_id = $1`,
+ [userId, providerId]
+ );
+ }
+
+ logger.info(`Created new user ${userId} with Telegram account ${ctx.from.id}`);
+ }
+ }
+ }
+
+ // ----> НАЧАЛО: Проверка роли на основе привязанного кошелька <----
+ if (userId) { // Убедимся, что userId определен
+ logger.info(`[TelegramBot] Checking linked wallet for determined userId: ${userId} (Type: ${typeof userId})`);
+ try {
+ const linkedWallet = await authService.getLinkedWallet(userId);
+ if (linkedWallet) {
+ logger.info(`[TelegramBot] Found linked wallet ${linkedWallet} for user ${userId}. Checking role...`);
+ const isAdmin = await authService.checkAdminRole(linkedWallet);
+ userRole = isAdmin ? 'admin' : 'user';
+ logger.info(`[TelegramBot] Role for user ${userId} determined as: ${userRole}`);
+
+ // Опционально: Обновить роль в таблице users
+ const currentUser = await db.getQuery()('SELECT role FROM users WHERE id = $1', [userId]);
+ if (currentUser.rows.length > 0 && currentUser.rows[0].role !== userRole) {
+ await db.getQuery()('UPDATE users SET role = $1 WHERE id = $2', [userRole, userId]);
+ logger.info(`[TelegramBot] Updated user role in DB to ${userRole}`);
+ }
+ } else {
+ logger.info(`[TelegramBot] No linked wallet found for user ${userId}. Checking current DB role.`);
+ // Если кошелька нет, берем текущую роль из базы
+ const currentUser = await db.getQuery()('SELECT role FROM users WHERE id = $1', [userId]);
+ if (currentUser.rows.length > 0) {
+ userRole = currentUser.rows[0].role;
+ }
+ }
+ } catch (roleCheckError) {
+ logger.error(`[TelegramBot] Error checking admin role for user ${userId}:`, roleCheckError);
+ // В случае ошибки берем роль из базы или оставляем 'user'
+ try {
+ const currentUser = await db.getQuery()('SELECT role FROM users WHERE id = $1', [userId]);
+ if (currentUser.rows.length > 0) { userRole = currentUser.rows[0].role; }
+ } catch (dbError) { /* ignore */ }
+ }
+ } else {
+ logger.error('[TelegramBot] Cannot check role because userId is undefined!');
+ }
+ // ----> КОНЕЦ: Проверка роли <----
+
+ // Логируем userId перед обновлением сессии
+ logger.info(`[telegramBot] Attempting to update session for userId: ${userId}`);
+
+ // Находим последнюю активную сессию для данного userId
+ let activeSessionId = null;
+ try {
+ // Ищем сессию, где есть userId и она не истекла (проверка expires_at)
+ // Сортируем по expires_at DESC чтобы взять самую "свежую", если их несколько
+ const sessionResult = await db.getQuery()(
+ `SELECT sid FROM session
+ WHERE sess ->> 'userId' = $1
+ AND expire > NOW()
+ ORDER BY expire DESC
+ LIMIT 1`,
+ [userId?.toString()] // Используем optional chaining и преобразуем в строку
);
- if (updateResult.rowCount > 0) {
- logger.info(`[telegramBot] Session ${activeSessionId} updated successfully with Telegram data for user ${userId}`);
+ if (sessionResult.rows.length > 0) {
+ activeSessionId = sessionResult.rows[0].sid;
+ logger.info(`[telegramBot] Found active session ID ${activeSessionId} for user ${userId}`);
+
+ // Обновляем найденную сессию в базе данных, добавляя/перезаписывая данные Telegram
+ const updateResult = await db.getQuery()(
+ `UPDATE session
+ SET sess = (sess::jsonb || $1::jsonb)::json
+ WHERE sid = $2`,
+ [
+ JSON.stringify({
+ // authenticated: true, // Не перезаписываем, т.к. сессия уже должна быть аутентифицирована
+ authType: 'telegram', // Обновляем тип аутентификации
+ telegramId: ctx.from.id.toString(),
+ telegramUsername: ctx.from.username,
+ telegramFirstName: ctx.from.first_name,
+ role: userRole, // Записываем определенную роль
+ // userId: userId?.toString() // userId уже должен быть в сессии
+ }),
+ activeSessionId // Обновляем по найденному session ID
+ ]
+ );
+
+ if (updateResult.rowCount > 0) {
+ logger.info(`[telegramBot] Session ${activeSessionId} updated successfully with Telegram data for user ${userId}`);
+ } else {
+ logger.warn(`[telegramBot] Session update query executed but did not update rows for sid: ${activeSessionId}. This might indicate a concurrency issue or incorrect sid.`);
+ }
+
} else {
- logger.warn(`[telegramBot] Session update query executed but did not update rows for sid: ${activeSessionId}. This might indicate a concurrency issue or incorrect sid.`);
+ logger.warn(`[telegramBot] No active web session found for userId: ${userId}. Telegram is linked, but the user might need to refresh their browser session.`);
}
-
- } else {
- logger.warn(`[telegramBot] No active web session found for userId: ${userId}. Telegram is linked, but the user might need to refresh their browser session.`);
+ } catch(sessionError) {
+ logger.error(`[telegramBot] Error finding or updating session for userId ${userId}:`, sessionError);
}
- } catch(sessionError) {
- logger.error(`[telegramBot] Error finding or updating session for userId ${userId}:`, sessionError);
- }
- // Отправляем сообщение об успешной аутентификации
- await ctx.reply('Аутентификация успешна! Можете вернуться в приложение.');
+ // Отправляем сообщение об успешной аутентификации
+ await ctx.reply('Аутентификация успешна! Можете вернуться в приложение.');
- // Удаляем сообщение с кодом
- try {
- await ctx.deleteMessage(ctx.message.message_id);
+ // Удаляем сообщение с кодом
+ try {
+ await ctx.deleteMessage(ctx.message.message_id);
+ } catch (error) {
+ logger.warn('Could not delete code message:', error);
+ }
} catch (error) {
- logger.warn('Could not delete code message:', error);
+ logger.error('Error in Telegram auth:', error);
+ await ctx.reply('Произошла ошибка при аутентификации. Попробуйте позже.');
}
+ return;
+ }
+ // 3. Всё остальное — чат с ИИ-ассистентом
+ try {
+ const telegramId = ctx.from.id.toString();
+ // 1. Найти или создать пользователя
+ const { userId, role } = await identityService.findOrCreateUserWithRole('telegram', telegramId);
+
+ // 2. Сохранить входящее сообщение в messages
+ let content = text;
+ let attachmentMeta = {};
+ // Проверяем вложения (фото, документ, аудио, видео)
+ let fileId, fileName, mimeType, fileSize, attachmentBuffer;
+ if (ctx.message.document) {
+ fileId = ctx.message.document.file_id;
+ fileName = ctx.message.document.file_name;
+ mimeType = ctx.message.document.mime_type;
+ fileSize = ctx.message.document.file_size;
+ } else if (ctx.message.photo && ctx.message.photo.length > 0) {
+ // Берём самое большое фото
+ const photo = ctx.message.photo[ctx.message.photo.length - 1];
+ fileId = photo.file_id;
+ fileName = 'photo.jpg';
+ mimeType = 'image/jpeg';
+ fileSize = photo.file_size;
+ } else if (ctx.message.audio) {
+ fileId = ctx.message.audio.file_id;
+ fileName = ctx.message.audio.file_name || 'audio.ogg';
+ mimeType = ctx.message.audio.mime_type || 'audio/ogg';
+ fileSize = ctx.message.audio.file_size;
+ } else if (ctx.message.video) {
+ fileId = ctx.message.video.file_id;
+ fileName = ctx.message.video.file_name || 'video.mp4';
+ mimeType = ctx.message.video.mime_type || 'video/mp4';
+ fileSize = ctx.message.video.file_size;
+ }
+ if (fileId) {
+ // Скачиваем файл
+ const fileLink = await ctx.telegram.getFileLink(fileId);
+ const res = await fetch(fileLink.href);
+ attachmentBuffer = await res.buffer();
+ attachmentMeta = {
+ attachment_filename: fileName,
+ attachment_mimetype: mimeType,
+ attachment_size: fileSize,
+ attachment_data: attachmentBuffer
+ };
+ }
+ // Сохраняем сообщение в БД
+ await db.getQuery()(
+ `INSERT INTO messages (user_id, sender_type, content, channel, role, direction, created_at, attachment_filename, attachment_mimetype, attachment_size, attachment_data)
+ VALUES ($1, $2, $3, $4, $5, $6, NOW(), $7, $8, $9, $10)`,
+ [userId, 'user', content, 'telegram', role, 'in',
+ attachmentMeta.attachment_filename || null,
+ attachmentMeta.attachment_mimetype || null,
+ attachmentMeta.attachment_size || null,
+ attachmentMeta.attachment_data || null
+ ]
+ );
+
+ // 3. Получить ответ от ИИ
+ const aiResponse = await aiAssistant.getResponse(content, 'auto');
+ // 4. Сохранить ответ в БД
+ await db.getQuery()(
+ `INSERT INTO messages (user_id, sender_type, content, channel, role, direction, created_at)
+ VALUES ($1, $2, $3, $4, $5, $6, NOW())`,
+ [userId, 'assistant', aiResponse, 'telegram', role, 'out']
+ );
+ // 5. Отправить ответ пользователю
+ await ctx.reply(aiResponse);
} catch (error) {
- logger.error('Error in Telegram auth:', error);
- await ctx.reply('Произошла ошибка при аутентификации. Попробуйте позже.');
+ logger.error('[TelegramBot] Ошибка при обработке сообщения:', error);
+ await ctx.reply('Произошла ошибка при обработке вашего сообщения. Попробуйте позже.');
}
});
diff --git a/backend/services/wallet-service.js b/backend/services/wallet-service.js
new file mode 100644
index 0000000..3fcd0d5
--- /dev/null
+++ b/backend/services/wallet-service.js
@@ -0,0 +1,24 @@
+const db = require('../db');
+const logger = require('../utils/logger');
+
+// Получение связанного кошелька
+async function getLinkedWallet(userId) {
+ logger.info(`[getLinkedWallet] Called with userId: ${userId} (Type: ${typeof userId})`);
+ try {
+ const result = await db.getQuery()(
+ `SELECT provider_id as address
+ FROM user_identities
+ WHERE user_id = $1 AND provider = 'wallet'`,
+ [userId]
+ );
+ logger.info(`[getLinkedWallet] DB query result for userId ${userId}:`, result.rows);
+ const address = result.rows[0]?.address;
+ logger.info(`[getLinkedWallet] Returning address: ${address} for userId ${userId}`);
+ return address;
+ } catch (error) {
+ logger.error(`[getLinkedWallet] Error fetching linked wallet for userId ${userId}:`, error);
+ return undefined;
+ }
+}
+
+module.exports = { getLinkedWallet };
\ No newline at end of file
diff --git a/frontend/src/components/ContactTable.vue b/frontend/src/components/ContactTable.vue
new file mode 100644
index 0000000..7134d57
--- /dev/null
+++ b/frontend/src/components/ContactTable.vue
@@ -0,0 +1,122 @@
+
+ Адрес: {{ shortenAddress(dle.tokenAddress) }} Местонахождение: {{ dle.location }} {{ selectedDle.tokenAddress }} {{ selectedDle.timelockAddress }} {{ selectedDle.governorAddress }} Предложений пока нет {{ proposal.description }} Здесь вы можете подключить дополнительные модули к вашему DLE. {{ module.description }}Контакты
+
+
+
+
+
+
+
+
+ Имя
+ Email
+ Telegram
+ Кошелек
+ Дата создания
+
+
+
+ {{ contact.name || '-' }}
+ {{ contact.email || '-' }}
+ {{ contact.telegram || '-' }}
+ {{ contact.wallet || '-' }}
+ {{ formatDate(contact.created_at) }}
+ Ваши DLE
+
+ {{ dle.name }} ({{ dle.symbol }})
+ Управление "{{ selectedDle.name }}"
+ Основная информация
+ Токен управления
+ Таймлок
+ Governor
+ Предложения
+ Новое предложение
+ {{ proposal.title }}
+ Управление
+ Настройки Governor
+ Статистика голосований
+ Подключение модулей
+ {{ module.name }}
+
Загрузка данных DLE...
- +Для доступа к управлению DLE необходимо .
-Адрес: {{ shortenAddress(dle.tokenAddress) }}
-Местонахождение: {{ dle.location }}
-{{ selectedDle.tokenAddress }}
-{{ selectedDle.timelockAddress }}
-{{ selectedDle.governorAddress }}
-Предложений пока нет
-{{ proposal.description }}
-Здесь вы можете подключить дополнительные модули к вашему DLE.
- -{{ module.description }}
-