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

This commit is contained in:
2025-04-21 18:03:43 +03:00
parent 4648aab7d5
commit 43569ea38c
37 changed files with 7226 additions and 1425 deletions

View File

@@ -26,34 +26,38 @@ app.set('host', '0.0.0.0');
app.set('port', process.env.PORT || 8000);
// Настройка CORS
app.use(cors({
origin: [
'http://localhost:5173',
'http://127.0.0.1:5173' // Добавляем альтернативный origin
],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'Cookie']
}));
app.use(
cors({
origin: [
'http://localhost:5173',
'http://127.0.0.1:5173', // Добавляем альтернативный origin
],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'Cookie'],
})
);
// Настройка сессии
app.use(session({
store: new pgSession({
pool,
tableName: 'session',
}),
secret: process.env.SESSION_SECRET || 'hb3atoken',
name: 'sessionId',
resave: false,
saveUninitialized: true,
cookie: {
maxAge: 30 * 24 * 60 * 60 * 1000,
httpOnly: true,
secure: false,
sameSite: 'lax',
path: '/'
}
}));
app.use(
session({
store: new pgSession({
pool,
tableName: 'session',
}),
secret: process.env.SESSION_SECRET || 'hb3atoken',
name: 'sessionId',
resave: false,
saveUninitialized: true,
cookie: {
maxAge: 30 * 24 * 60 * 60 * 1000,
httpOnly: true,
secure: false,
sameSite: 'lax',
path: '/',
},
})
);
// Добавим middleware для проверки сессии
app.use(async (req, res, next) => {
@@ -62,10 +66,7 @@ app.use(async (req, res, next) => {
// Проверяем сессию в базе данных
if (req.sessionID) {
const result = await pool.query(
'SELECT sess FROM session WHERE sid = $1',
[req.sessionID]
);
const result = await pool.query('SELECT sess FROM session WHERE sid = $1', [req.sessionID]);
console.log('Session from DB:', result.rows[0]?.sess);
}
@@ -80,13 +81,16 @@ app.use(async (req, res, next) => {
const token = authHeader.split(' ')[1];
try {
// Находим пользователя по токену
const { rows } = await pool.query(`
const { rows } = await pool.query(
`
SELECT u.id,
(u.role = 'admin') as is_admin,
u.address
FROM users u
WHERE u.id = $1
`, [token]);
`,
[token]
);
if (rows.length > 0) {
const user = rows[0];
@@ -95,7 +99,7 @@ app.use(async (req, res, next) => {
req.session.isAdmin = user.is_admin;
req.session.authenticated = true;
await new Promise(resolve => req.session.save(resolve));
await new Promise((resolve) => req.session.save(resolve));
}
} catch (error) {
console.error('Error checking auth header:', error);
@@ -110,9 +114,11 @@ app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Настройка безопасности
app.use(helmet({
contentSecurityPolicy: false // Отключаем CSP для разработки
}));
app.use(
helmet({
contentSecurityPolicy: false, // Отключаем CSP для разработки
})
);
// Логирование запросов
app.use((req, res, next) => {
@@ -164,24 +170,27 @@ app.get('/api/health', async (req, res) => {
status: 'ok',
timestamp: new Date().toISOString(),
database: 'connected',
ai: aiStatus
ai: aiStatus,
});
} catch (error) {
logger.error('Health check failed:', error);
res.status(500).json({
status: 'error',
error: error.message
error: error.message,
});
}
});
// Очистка старых сессий
setInterval(async () => {
try {
await pool.query('DELETE FROM session WHERE expire < NOW()');
} catch (error) {
console.error('Error cleaning old sessions:', error);
}
}, 15 * 60 * 1000); // Каждые 15 минут
setInterval(
async () => {
try {
await pool.query('DELETE FROM session WHERE expire < NOW()');
} catch (error) {
console.error('Error cleaning old sessions:', error);
}
},
15 * 60 * 1000
); // Каждые 15 минут
module.exports = { app, nonceStore };

View File

@@ -16,10 +16,10 @@ const sessionConfig = {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/'
}
path: '/',
},
};
module.exports = {
sessionMiddleware: session(sessionConfig)
sessionMiddleware: session(sessionConfig),
};

View File

@@ -56,10 +56,13 @@ const query = (text, params) => {
// Функция для сохранения гостевого сообщения в базе данных
async function saveGuestMessageToDatabase(message, language, guestId) {
try {
await query(`
await query(
`
INSERT INTO guest_messages (guest_id, content, language, created_at)
VALUES ($1, $2, $3, NOW())
`, [guestId, message, language]);
`,
[guestId, message, language]
);
console.log('Гостевое сообщение успешно сохранено:', message);
} catch (error) {
console.error('Ошибка при сохранении гостевого сообщения:', error);

View File

@@ -41,8 +41,12 @@ async function initRoles() {
if (rolesExist.rows[0].count < 2) {
// Добавляем недостающие роли
const userRoleExists = await pool.query(`SELECT EXISTS (SELECT FROM roles WHERE name = 'user');`);
const adminRoleExists = await pool.query(`SELECT EXISTS (SELECT FROM roles WHERE name = 'admin');`);
const userRoleExists = await pool.query(
`SELECT EXISTS (SELECT FROM roles WHERE name = 'user');`
);
const adminRoleExists = await pool.query(
`SELECT EXISTS (SELECT FROM roles WHERE name = 'admin');`
);
if (!userRoleExists.rows[0].exists) {
await pool.query(`
@@ -82,13 +86,14 @@ async function initializeDatabase() {
const migrationsPath = path.join(__dirname, 'migrations');
// Получаем все файлы миграций
const migrationFiles = fs.readdirSync(migrationsPath)
.filter(file => file.endsWith('.sql'))
const migrationFiles = fs
.readdirSync(migrationsPath)
.filter((file) => file.endsWith('.sql'))
.sort();
// Получаем выполненные миграции
const { rows } = await pool.query('SELECT name FROM migrations');
const executedMigrations = new Set(rows.map(row => row.name));
const executedMigrations = new Set(rows.map((row) => row.name));
// Выполняем только новые миграции
for (const file of migrationFiles) {
@@ -100,10 +105,7 @@ async function initializeDatabase() {
await pool.query(sql);
// Записываем выполненную миграцию
await pool.query(
'INSERT INTO migrations (name) VALUES ($1)',
[file]
);
await pool.query('INSERT INTO migrations (name) VALUES ($1)', [file]);
logger.info(`Migration completed: ${file}`);
}

View File

@@ -7,6 +7,7 @@
Система идентификации пользователей построена на следующих таблицах:
1. **users** - Основная таблица пользователей
- `id SERIAL PRIMARY KEY` - Основной идентификатор пользователя
- `status` - Статус пользователя (active, blocked)
- `role` - Роль пользователя (user, admin)
@@ -14,6 +15,7 @@
- Поля `username`, `email` и `address` являются устаревшими и должны быть NULL
2. **user_identities** - Таблица идентификаторов пользователей
- `id SERIAL PRIMARY KEY` - Идентификатор записи
- `user_id INTEGER REFERENCES users(id)` - Ссылка на пользователя
- `provider VARCHAR(50)` - Тип идентификатора (email, wallet, telegram, username)
@@ -22,6 +24,7 @@
- Ограничение `CHECK (provider IN ('email', 'wallet', 'telegram', 'username'))` - запрещает тип 'guest'
3. **guest_user_mapping** - Таблица связи гостевых идентификаторов с пользователями
- `id SERIAL PRIMARY KEY` - Идентификатор записи
- `user_id INTEGER REFERENCES users(id)` - Ссылка на пользователя
- `guest_id VARCHAR(255)` - Гостевой идентификатор
@@ -29,6 +32,7 @@
- Уникальный ключ `guest_id`
4. **messages** - Таблица сообщений
- `id SERIAL PRIMARY KEY` - Идентификатор сообщения
- `conversation_id INTEGER REFERENCES conversations(id)` - Ссылка на диалог
- `user_id INTEGER REFERENCES users(id)` - Прямая ссылка на пользователя
@@ -56,7 +60,6 @@
- Создается запись в таблице `users`
- Создается запись в таблице `user_identities` с соответствующим провайдером
- Гостевой ID сохраняется в таблице `guest_user_mapping` (не в user_identities)
2. После аутентификации система автоматически обрабатывает гостевые сообщения:
- Вызывается метод `linkGuestMessages`
- Создается новый диалог для гостевых сообщений
@@ -89,6 +92,7 @@
## Обработка ошибок
1. Если возникает ошибка при обработке гостевых сообщений, система:
- Логирует ошибку
- Продолжает попытки обработки при следующих авторизациях
- Не удаляет гостевые сообщения до успешной обработки
@@ -98,6 +102,7 @@
## Оптимизации
1. Индексы созданы для всех полей, используемых в запросах:
- `user_identities(user_id)`
- `user_identities(provider, provider_id)`
- `guest_user_mapping(guest_id)`
@@ -106,6 +111,7 @@
- `messages(conversation_id)`
2. Триггеры автоматически поддерживают целостность данных:
- Автоматическое заполнение `user_id` в таблице `messages`
- Очистка неиспользуемых полей в таблице `users`
@@ -117,6 +123,7 @@
## Функции для диагностики
1. **verify_migration_017()** - проверяет состояние гостевых идентификаторов
- `guest_identities_count` - количество гостевых идентификаторов в таблице user_identities
- `guest_mapping_count` - количество записей в таблице guest_user_mapping
- `missing_mappings` - количество гостевых ID, которые отсутствуют в guest_user_mapping

View File

@@ -69,6 +69,7 @@ END $$;
- `verify_identity_system()` - проверка состояния системы идентификации пользователей
Пример использования:
```sql
SELECT * FROM verify_identity_system();
```

View File

@@ -1,6 +1,6 @@
import globals from 'globals';
const globals = require('globals');
export default [
module.exports = [
{
ignores: ['node_modules/**', 'artifacts/**', 'sessions/**', 'logs/**', 'data/**'],
},
@@ -8,10 +8,11 @@ export default [
files: ['**/*.js'],
languageOptions: {
ecmaVersion: 2022,
sourceType: 'module',
sourceType: 'module', // Оставляем module, т.к. ESLint может анализировать ES модули
globals: {
...globals.node,
...globals.es2021,
// Для тестов Mocha
describe: 'readonly',
it: 'readonly',
beforeEach: 'readonly',
@@ -21,8 +22,8 @@ export default [
},
},
rules: {
'no-unused-vars': 'off',
'no-console': 'off',
'no-unused-vars': 'warn', // Лучше warn, чем off
'no-console': 'off', // Оставляем off для логов в Node.js
'no-undef': 'error',
'no-duplicate-imports': 'error',
},

View File

@@ -13,7 +13,7 @@ const requireAuth = async (req, res, next) => {
console.log('Session in requireAuth:', {
id: req.sessionID,
userId: req.session?.userId,
authenticated: req.session?.authenticated
authenticated: req.session?.authenticated,
});
// Проверяем сессию
@@ -25,7 +25,7 @@ const requireAuth = async (req, res, next) => {
userId: req.session.userId,
address: req.session.address,
isAdmin: req.session.isAdmin,
authType: req.session.authType
authType: req.session.authType,
};
return next();
}
@@ -36,13 +36,16 @@ const requireAuth = async (req, res, next) => {
const address = authHeader.split(' ')[1];
if (address.startsWith('0x')) {
const result = await db.query(`
const result = await db.query(
`
SELECT u.id, u.is_admin
FROM users u
JOIN user_identities ui ON u.id = ui.user_id
WHERE ui.identity_type = 'wallet'
AND LOWER(ui.identity_value) = LOWER($1)
`, [address]);
`,
[address]
);
if (result.rows.length > 0) {
const user = result.rows[0];
@@ -68,7 +71,7 @@ const requireAuth = async (req, res, next) => {
userId: user.id,
address: address,
isAdmin: user.is_admin,
authType: 'wallet'
authType: 'wallet',
};
next();
});
@@ -111,7 +114,9 @@ async function requireAdmin(req, res, next) {
// Проверка через ID пользователя
if (req.session.userId) {
const userResult = await db.query('SELECT role FROM users WHERE id = $1', [req.session.userId]);
const userResult = await db.query('SELECT role FROM users WHERE id = $1', [
req.session.userId,
]);
if (userResult.rows.length > 0 && userResult.rows[0].role === USER_ROLES.ADMIN) {
// Обновляем сессию
req.session.isAdmin = true;
@@ -146,7 +151,9 @@ function requireRole(role) {
// Проверка через ID пользователя
if (req.session.userId) {
const userResult = await db.query('SELECT role FROM users WHERE id = $1', [req.session.userId]);
const userResult = await db.query('SELECT role FROM users WHERE id = $1', [
req.session.userId,
]);
if (userResult.rows.length > 0 && userResult.rows[0].role === role) {
return next();
}
@@ -192,5 +199,5 @@ module.exports = {
requireAuth,
requireAdmin,
requireRole,
checkRole
checkRole,
};

View File

@@ -11,7 +11,7 @@ function errorHandler(err, req, res, next) {
url: req.originalUrl,
method: req.method,
ip: req.ip,
userId: req.session?.userId
userId: req.session?.userId,
});
// Определяем тип ошибки
@@ -39,17 +39,15 @@ function errorHandler(err, req, res, next) {
}
// В режиме разработки возвращаем стек ошибки
const devError = process.env.NODE_ENV === 'development'
? { stack: err.stack }
: {};
const devError = process.env.NODE_ENV === 'development' ? { stack: err.stack } : {};
// Отправляем ответ клиенту
res.status(statusCode).json({
error: {
code: errorCode,
message: errorMessage,
...devError
}
...devError,
},
});
}
@@ -67,5 +65,5 @@ function createError(message, status) {
module.exports = {
errorHandler,
createError
createError,
};

View File

@@ -1,5 +1,6 @@
const express = require('express');
const router = express.Router();
const db = require('../db');
const { requireAdmin } = require('../middleware/auth');
const authService = require('../services/auth-service');
const logger = require('../utils/logger');

View File

@@ -37,21 +37,20 @@ router.get('/nonce', async (req, res) => {
const nonce = crypto.randomBytes(16).toString('hex');
// Проверяем, существует ли уже nonce для этого адреса
const existingNonce = await db.query(
'SELECT id FROM nonces WHERE identity_value = $1',
[address.toLowerCase()]
);
const existingNonce = await db.query('SELECT id FROM nonces WHERE identity_value = $1', [
address.toLowerCase(),
]);
if (existingNonce.rows.length > 0) {
// Обновляем существующий nonce
await db.query(
'UPDATE nonces SET nonce = $1, expires_at = NOW() + INTERVAL \'15 minutes\' WHERE identity_value = $2',
"UPDATE nonces SET nonce = $1, expires_at = NOW() + INTERVAL '15 minutes' WHERE identity_value = $2",
[nonce, address.toLowerCase()]
);
} else {
// Создаем новый nonce
await db.query(
'INSERT INTO nonces (identity_value, nonce, expires_at) VALUES ($1, $2, NOW() + INTERVAL \'15 minutes\')',
"INSERT INTO nonces (identity_value, nonce, expires_at) VALUES ($1, $2, NOW() + INTERVAL '15 minutes')",
[address.toLowerCase(), nonce]
);
}
@@ -86,8 +85,13 @@ router.post('/verify', async (req, res) => {
const normalizedAddress = ethers.getAddress(address).toLowerCase();
// Проверяем nonce
const nonceResult = await db.query('SELECT nonce FROM nonces WHERE identity_value = $1', [normalizedAddress]);
if (nonceResult.rows.length === 0 || nonceResult.rows[0].nonce !== message.match(/Nonce: ([^\n]+)/)[1]) {
const nonceResult = await db.query('SELECT nonce FROM nonces WHERE identity_value = $1', [
normalizedAddress,
]);
if (
nonceResult.rows.length === 0 ||
nonceResult.rows[0].nonce !== message.match(/Nonce: ([^\n]+)/)[1]
) {
return res.status(401).json({ success: false, error: 'Invalid nonce' });
}
@@ -98,24 +102,24 @@ router.post('/verify', async (req, res) => {
if (req.session.authenticated && req.session.userId) {
// Если пользователь уже авторизован, привязываем кошелек к существующему пользователю
userId = req.session.userId;
logger.info(`[verify] Using existing authenticated user ${userId} for wallet ${normalizedAddress}`);
logger.info(
`[verify] Using existing authenticated user ${userId} for wallet ${normalizedAddress}`
);
// Связываем кошелек с пользователем через identity-service для предотвращения дубликатов
const linkResult = await authService.linkIdentity(
userId,
'wallet',
address
);
const linkResult = await authService.linkIdentity(userId, 'wallet', address);
if (!linkResult.success && linkResult.error) {
return res.status(400).json({
success: false,
error: linkResult.error
error: linkResult.error,
});
}
// Если linkResult.message содержит 'already exists', значит кошелек уже привязан
logger.info(`[verify] Wallet ${normalizedAddress} linked to user ${userId}: ${linkResult.message || 'success'}`);
logger.info(
`[verify] Wallet ${normalizedAddress} linked to user ${userId}: ${linkResult.message || 'success'}`
);
} else {
// Находим или создаем пользователя, если не авторизован
const result = await authService.findOrCreateUser(address);
@@ -163,9 +167,8 @@ router.post('/verify', async (req, res) => {
userId,
address: normalizedAddress, // Возвращаем нормализованный адрес
isAdmin: adminStatus || isAdmin,
authenticated: true
authenticated: true,
});
} catch (error) {
logger.error('[verify] Error:', error);
res.status(500).json({ success: false, error: 'Server error' });
@@ -180,7 +183,7 @@ router.post('/telegram/verify', async (req, res) => {
if (!telegramId || !verificationCode) {
return res.status(400).json({
success: false,
error: 'Missing required fields'
error: 'Missing required fields',
});
}
@@ -197,7 +200,7 @@ router.post('/telegram/verify', async (req, res) => {
if (!verificationResult.success) {
return res.status(400).json({
success: false,
error: verificationResult.error || 'Verification failed'
error: verificationResult.error || 'Verification failed',
});
}
@@ -207,7 +210,7 @@ router.post('/telegram/verify', async (req, res) => {
logger.error('[telegram/verify] Error regenerating session:', err);
return res.status(500).json({
success: false,
error: 'Session error'
error: 'Session error',
});
}
@@ -236,14 +239,14 @@ router.post('/telegram/verify', async (req, res) => {
userId: verificationResult.userId,
role: verificationResult.role,
telegramId,
isNewUser: verificationResult.isNewUser
isNewUser: verificationResult.isNewUser,
});
});
} catch (error) {
logger.error('[telegram/verify] Error:', error);
return res.status(500).json({
success: false,
error: 'Internal server error'
error: 'Internal server error',
});
}
});
@@ -266,12 +269,12 @@ router.post('/email/request', authLimiter, async (req, res) => {
if (result.success) {
res.json({
success: true,
message: 'Код подтверждения отправлен на email'
message: 'Код подтверждения отправлен на email',
});
} else {
res.status(500).json({
success: false,
error: result.error || 'Ошибка отправки кода'
error: result.error || 'Ошибка отправки кода',
});
}
} catch (error) {
@@ -288,7 +291,7 @@ router.post('/email/verify-code', async (req, res) => {
if (!code) {
return res.status(400).json({
success: false,
error: 'Код подтверждения обязателен'
error: 'Код подтверждения обязателен',
});
}
@@ -300,7 +303,7 @@ router.post('/email/verify-code', async (req, res) => {
if (!req.session.pendingEmail) {
return res.status(400).json({
success: false,
error: 'Email не найден в сессии. Пожалуйста, запросите код подтверждения снова.'
error: 'Email не найден в сессии. Пожалуйста, запросите код подтверждения снова.',
});
}
@@ -318,7 +321,7 @@ router.post('/email/verify-code', async (req, res) => {
if (!verificationResult.success) {
return res.status(400).json({
success: false,
error: verificationResult.error || 'Неверный код подтверждения'
error: verificationResult.error || 'Неверный код подтверждения',
});
}
@@ -330,14 +333,12 @@ router.post('/email/verify-code', async (req, res) => {
if (req.session.authenticated && req.session.userId) {
// Связываем email с существующим пользователем
userId = req.session.userId;
logger.info(`[email/verify-code] Linking email ${req.session.pendingEmail} to existing authenticated user ${userId}`);
logger.info(
`[email/verify-code] Linking email ${req.session.pendingEmail} to existing authenticated user ${userId}`
);
// Связываем email с текущим аккаунтом
const linkResult = await authService.linkIdentity(
userId,
'email',
req.session.pendingEmail
);
const linkResult = await authService.linkIdentity(userId, 'email', req.session.pendingEmail);
// Сохраняем email в сессии
req.session.email = req.session.pendingEmail;
@@ -353,7 +354,7 @@ router.post('/email/verify-code', async (req, res) => {
userId,
email: req.session.email,
authenticated: true,
linked: true
linked: true,
});
} else {
// Если пользователь не авторизован, ищем существующего пользователя или создаем нового
@@ -367,24 +368,31 @@ router.post('/email/verify-code', async (req, res) => {
if (existingUser) {
// Используем существующего пользователя
userId = existingUser.id;
logger.info(`[email/verify-code] Using existing user ${userId} with email ${req.session.pendingEmail}`);
logger.info(
`[email/verify-code] Using existing user ${userId} with email ${req.session.pendingEmail}`
);
} else if (req.session.userId) {
// Используем текущего пользователя
userId = req.session.userId;
logger.info(`[email/verify-code] Using current user ${userId} for email ${req.session.pendingEmail}`);
logger.info(
`[email/verify-code] Using current user ${userId} for email ${req.session.pendingEmail}`
);
} else if (req.session.tempUserId) {
// Используем временного пользователя
userId = req.session.tempUserId;
logger.info(`[email/verify-code] Using temporary user ${userId} for email ${req.session.pendingEmail}`);
logger.info(
`[email/verify-code] Using temporary user ${userId} for email ${req.session.pendingEmail}`
);
} else {
// Создаем нового пользователя
const newUser = await db.query(
'INSERT INTO users (role) VALUES ($1) RETURNING id',
['user']
);
const newUser = await db.query('INSERT INTO users (role) VALUES ($1) RETURNING id', [
'user',
]);
userId = newUser.rows[0].id;
isNewAuth = true;
logger.info(`[email/verify-code] Created new user ${userId} for email ${req.session.pendingEmail}`);
logger.info(
`[email/verify-code] Created new user ${userId} for email ${req.session.pendingEmail}`
);
}
// Сохраняем email как идентификатор
@@ -420,14 +428,14 @@ router.post('/email/verify-code', async (req, res) => {
userId,
email: req.session.email,
authenticated: true,
isNewAuth
isNewAuth,
});
}
} catch (error) {
logger.error('[email/verify-code] Error:', error);
return res.status(500).json({
success: false,
error: 'Ошибка сервера'
error: 'Ошибка сервера',
});
}
});
@@ -444,7 +452,7 @@ router.post('/telegram/init', async (req, res) => {
res.json({
success: true,
verificationCode,
botLink
botLink,
});
} catch (error) {
logger.error('Error initializing Telegram auth:', error);
@@ -452,13 +460,13 @@ router.post('/telegram/init', async (req, res) => {
if (error.message === 'Telegram уже привязан к этому аккаунту') {
return res.status(400).json({
success: false,
error: error.message
error: error.message,
});
}
res.status(500).json({
success: false,
error: 'Failed to initialize Telegram auth'
error: 'Failed to initialize Telegram auth',
});
}
});
@@ -471,7 +479,7 @@ router.post('/email/init', async (req, res) => {
if (!email || !email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
return res.status(400).json({
success: false,
error: 'Некорректный формат email'
error: 'Некорректный формат email',
});
}
@@ -483,13 +491,13 @@ router.post('/email/init', async (req, res) => {
return res.json({
success: true,
message: 'Код верификации отправлен на email'
message: 'Код верификации отправлен на email',
});
} catch (error) {
logger.error('Error in email auth initialization:', error);
res.status(500).json({
success: false,
error: 'Внутренняя ошибка сервера'
error: 'Внутренняя ошибка сервера',
});
}
});
@@ -509,10 +517,9 @@ router.get('/check', async (req, res) => {
identities = await identityService.getUserIdentities(req.session.userId);
// Проверяем роль пользователя
const roleResult = await db.query(
'SELECT role FROM users WHERE id = $1',
[req.session.userId]
);
const roleResult = await db.query('SELECT role FROM users WHERE id = $1', [
req.session.userId,
]);
if (roleResult.rows.length > 0) {
isAdmin = roleResult.rows[0].role === 'admin';
@@ -539,7 +546,7 @@ router.get('/check', async (req, res) => {
guestId: req.session.guestId || null,
authType,
identitiesCount: identities.length,
isAdmin: isAdmin || false
isAdmin: isAdmin || false,
};
// Добавляем специфические поля в зависимости от типа аутентификации
@@ -562,7 +569,7 @@ router.get('/check', async (req, res) => {
logger.error('[session/check] Error:', error);
return res.status(500).json({
success: false,
error: 'Internal server error'
error: 'Internal server error',
});
}
});
@@ -618,7 +625,7 @@ router.get('/check-access', requireAuth, async (req, res) => {
success: true,
isAdmin,
userId,
address
address,
});
}
@@ -626,9 +633,8 @@ router.get('/check-access', requireAuth, async (req, res) => {
success: true,
isAdmin: false,
userId,
address: null
address: null,
});
} catch (error) {
logger.error('Error checking access:', error);
return res.status(500).json({ error: 'Internal server error' });
@@ -689,7 +695,7 @@ router.post('/wallet', async (req, res) => {
if (!address || !nonce || !signature) {
return res.status(400).json({
success: false,
error: 'Missing required fields'
error: 'Missing required fields',
});
}
@@ -705,7 +711,7 @@ router.post('/wallet', async (req, res) => {
if (!validSignature) {
return res.status(401).json({
success: false,
error: 'Invalid signature'
error: 'Invalid signature',
});
}
@@ -717,10 +723,7 @@ router.post('/wallet', async (req, res) => {
// Обновляем роль пользователя в базе данных, если нужно
if (isAdmin) {
await db.query(
'UPDATE users SET role = $1 WHERE id = $2',
['admin', userId]
);
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
}
// Сохраняем идентификаторы
@@ -753,14 +756,13 @@ router.post('/wallet', async (req, res) => {
userId,
address,
isAdmin,
authenticated: true
authenticated: true,
});
} catch (error) {
logger.error('[wallet] Error:', error);
res.status(500).json({
success: false,
error: 'Server error during wallet authentication'
error: 'Server error during wallet authentication',
});
}
});
@@ -775,13 +777,13 @@ router.get('/identities', requireAuth, async (req, res) => {
res.json({
success: true,
identities
identities,
});
} catch (error) {
logger.error('Error getting user identities:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
error: 'Internal server error',
});
}
});
@@ -798,13 +800,13 @@ router.get('/check-session', async (req, res) => {
res.json({
success: true,
guestId: req.session.guestId,
isAuthenticated: req.session.authenticated || false
isAuthenticated: req.session.authenticated || false,
});
} catch (error) {
logger.error('Error checking session:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
error: 'Internal server error',
});
}
});
@@ -819,13 +821,13 @@ router.get('/check-tokens/:address', async (req, res) => {
res.json({
success: true,
balances
balances,
});
} catch (error) {
logger.error('Error checking token balances:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
error: 'Internal server error',
});
}
});

View File

@@ -44,10 +44,9 @@ async function processGuestMessages(userId, guestId) {
console.log('No guest messages found');
// Помечаем как обработанные, даже если сообщений нет
await db.query(
'UPDATE guest_user_mapping SET processed = true WHERE guest_id = $1',
[guestId]
);
await db.query('UPDATE guest_user_mapping SET processed = true WHERE guest_id = $1', [
guestId,
]);
return { success: true, message: 'No guest messages found' };
}
@@ -57,9 +56,10 @@ async function processGuestMessages(userId, guestId) {
// Создаем новый диалог для этих сообщений
const firstMessage = guestMessages[0];
const title = firstMessage.content.length > 30
? `${firstMessage.content.substring(0, 30)}...`
: firstMessage.content;
const title =
firstMessage.content.length > 30
? `${firstMessage.content.substring(0, 30)}...`
: firstMessage.content;
const newConversationResult = await db.query(
'INSERT INTO conversations (user_id, title) VALUES ($1, $2) RETURNING *',
@@ -91,7 +91,7 @@ async function processGuestMessages(userId, guestId) {
'user',
'web',
guestMessage.created_at,
userId // Добавляем userId в сообщение для прямой связи
userId, // Добавляем userId в сообщение для прямой связи
]
);
@@ -119,7 +119,7 @@ async function processGuestMessages(userId, guestId) {
'assistant',
'web',
new Date(),
userId // Добавляем userId в сообщение для прямой связи
userId, // Добавляем userId в сообщение для прямой связи
]
);
@@ -134,13 +134,14 @@ async function processGuestMessages(userId, guestId) {
// Удаляем только успешно обработанные гостевые сообщения
if (savedMessageIds.length > 0) {
await db.query('DELETE FROM guest_messages WHERE id = ANY($1)', [savedMessageIds]);
console.log(`Deleted ${savedMessageIds.length} processed guest messages for guest ID ${guestId}`);
console.log(
`Deleted ${savedMessageIds.length} processed guest messages for guest ID ${guestId}`
);
// Помечаем гостевой ID как обработанный
await db.query(
'UPDATE guest_user_mapping SET processed = true WHERE guest_id = $1',
[guestId]
);
await db.query('UPDATE guest_user_mapping SET processed = true WHERE guest_id = $1', [
guestId,
]);
} else {
console.log('No guest messages were successfully processed, skipping deletion');
}
@@ -148,7 +149,7 @@ async function processGuestMessages(userId, guestId) {
return {
success: true,
message: `Processed ${savedMessageIds.length} of ${guestMessages.length} guest messages`,
conversationId: conversation.id
conversationId: conversation.id,
};
} catch (error) {
console.error('Error processing guest messages:', error);
@@ -184,7 +185,7 @@ router.post('/guest-message', async (req, res) => {
res.json({
success: true,
messageId: result.rows[0].id
messageId: result.rows[0].id,
});
} catch (error) {
console.error('Error saving guest message:', error);
@@ -201,7 +202,12 @@ router.post('/message', requireAuth, async (req, res) => {
}
try {
console.log('Processing message:', { message, conversationId, language, userId: req.session.userId });
console.log('Processing message:', {
message,
conversationId,
language,
userId: req.session.userId,
});
const userId = req.session.userId;
let conversation;
@@ -263,7 +269,7 @@ router.post('/message', requireAuth, async (req, res) => {
success: true,
userMessage: userMessageResult.rows[0],
aiMessage: aiMessageResult.rows[0],
conversation
conversation,
};
res.json(response);
@@ -296,7 +302,7 @@ router.get('/history', async (req, res) => {
userId: req.session.userId,
address: req.session.address,
authenticated: req.session.authenticated,
guestId: req.session.guestId
guestId: req.session.guestId,
});
const limit = parseInt(req.query.limit) || 50;
@@ -357,9 +363,8 @@ router.get('/history', async (req, res) => {
return res.json({
success: true,
messages: messages,
total: total
total: total,
});
} catch (error) {
logger.error('Error getting chat history:', error);
return res.status(500).json({ error: 'Internal server error' });

View File

@@ -39,7 +39,7 @@ router.post('/link', requireAuth, async (req, res) => {
if (existingUserId !== userId) {
return res.status(400).json({
success: false,
error: `This wallet (${value}) is already linked to another account`
error: `This wallet (${value}) is already linked to another account`,
});
}
}
@@ -60,7 +60,7 @@ router.post('/link', requireAuth, async (req, res) => {
res.json({
success: true,
message: 'Identity linked successfully',
isAdmin: req.session.isAdmin
isAdmin: req.session.isAdmin,
});
} catch (error) {
logger.error('Error linking identity:', error);
@@ -69,7 +69,7 @@ router.post('/link', requireAuth, async (req, res) => {
if (error.message && error.message.includes('already belongs to another user')) {
return res.status(400).json({
success: false,
error: `This identity is already linked to another account`
error: `This identity is already linked to another account`,
});
}
@@ -99,7 +99,7 @@ router.get('/token-balances', requireAuth, async (req, res) => {
res.json({
success: true,
balances
balances,
});
} catch (error) {
logger.error('Error getting token balances:', error);

View File

@@ -11,7 +11,7 @@ router.get('/balances', requireAuth, async (req, res) => {
if (!address) {
return res.status(400).json({
error: 'No wallet address in session'
error: 'No wallet address in session',
});
}
@@ -22,7 +22,7 @@ router.get('/balances', requireAuth, async (req, res) => {
} catch (error) {
logger.error('Error fetching token balances:', error);
res.status(500).json({
error: 'Failed to fetch token balances'
error: 'Failed to fetch token balances',
});
}
});

View File

@@ -31,10 +31,7 @@ router.post('/update-language', requireAuth, async (req, res) => {
}
// Обновление языка в базе данных
await db.query(
'UPDATE users SET preferred_language = $1 WHERE id = $2',
[language, userId]
);
await db.query('UPDATE users SET preferred_language = $1 WHERE id = $2', [language, userId]);
res.json({ success: true });
} catch (error) {
@@ -59,10 +56,11 @@ router.post('/update-profile', requireAuth, async (req, res) => {
}
// Обновление имени и фамилии в базе данных
await db.query(
'UPDATE users SET first_name = $1, last_name = $2 WHERE id = $3',
[firstName || null, lastName || null, userId]
);
await db.query('UPDATE users SET first_name = $1, last_name = $2 WHERE id = $3', [
firstName || null,
lastName || null,
userId,
]);
res.json({ success: true });
} catch (error) {
@@ -107,7 +105,7 @@ router.get('/profile/current', requireAuth, async (req, res) => {
status: user.status,
createdAt: user.created_at,
preferredLanguage: user.preferred_language,
identities
identities,
});
} catch (error) {
logger.error('Error getting user profile:', error);

View File

@@ -15,7 +15,7 @@ if (!fs.existsSync(logDir)) {
const logFile = path.join(logDir, 'fix-duplicates.log');
const logger = {
log: message => {
log: (message) => {
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}] ${message}\n`;
console.log(message);
@@ -27,7 +27,7 @@ const logger = {
const logMessage = `[${timestamp}] ERROR: ${message}${errorDetail}\n`;
console.error(`ERROR: ${message}${errorDetail}`);
fs.appendFileSync(logFile, logMessage);
}
},
};
// Создаем подключение к базе данных
@@ -116,25 +116,31 @@ async function fixDuplicates(duplicates) {
// Проверяем, что второй идентификатор в нормализованной форме
const remainingId = dup.provider_id1 === normalizedAddress ? dup.id1 : dup.id2;
const remainingAddress = dup.provider_id1 === normalizedAddress ? dup.provider_id1 : dup.provider_id2;
const remainingAddress =
dup.provider_id1 === normalizedAddress ? dup.provider_id1 : dup.provider_id2;
if (remainingAddress !== normalizedAddress) {
logger.log(`Обновление идентификатора ID ${remainingId} до нормализованного значения ${normalizedAddress}`);
await client.query(
'UPDATE user_identities SET provider_id = $1 WHERE id = $2',
[normalizedAddress, remainingId]
logger.log(
`Обновление идентификатора ID ${remainingId} до нормализованного значения ${normalizedAddress}`
);
await client.query('UPDATE user_identities SET provider_id = $1 WHERE id = $2', [
normalizedAddress,
remainingId,
]);
}
} else {
// Если идентификаторы принадлежат разным пользователям, нужно решить конфликт
// Для определения какой пользователь является основным, можно использовать:
// 1. Количество сообщений/активности
// 2. Дату создания аккаунта
logger.log(`Конфликт: адрес ${dup.provider_id1}/${dup.provider_id2} привязан к разным пользователям: ${dup.user_id1} и ${dup.user_id2}`);
logger.log(
`Конфликт: адрес ${dup.provider_id1}/${dup.provider_id2} привязан к разным пользователям: ${dup.user_id1} и ${dup.user_id2}`
);
// Определяем, какой пользователь является основным
const userInfoResult = await client.query(`
const userInfoResult = await client.query(
`
SELECT
id,
(SELECT COUNT(*) FROM messages WHERE user_id = users.id) as message_count,
@@ -145,7 +151,9 @@ async function fixDuplicates(duplicates) {
id IN ($1, $2)
ORDER BY
message_count DESC, created_at ASC
`, [dup.user_id1, dup.user_id2]);
`,
[dup.user_id1, dup.user_id2]
);
// Если нет пользователей, пропускаем
if (userInfoResult.rows.length === 0) {
@@ -157,23 +165,31 @@ async function fixDuplicates(duplicates) {
const mainUserId = userInfoResult.rows[0].id;
const secondaryUserId = mainUserId === dup.user_id1 ? dup.user_id2 : dup.user_id1;
logger.log(`Объединение пользователей: сохраняем ID ${mainUserId}, удаляем ID ${secondaryUserId}`);
logger.log(
`Объединение пользователей: сохраняем ID ${mainUserId}, удаляем ID ${secondaryUserId}`
);
// Переносим все идентификаторы от вторичного пользователя к основному
await client.query(`
await client.query(
`
INSERT INTO user_identities (user_id, provider, provider_id)
SELECT $1, provider, provider_id
FROM user_identities
WHERE user_id = $2
ON CONFLICT DO NOTHING
`, [mainUserId, secondaryUserId]);
`,
[mainUserId, secondaryUserId]
);
// Переносим сообщения
await client.query(`
await client.query(
`
UPDATE messages
SET user_id = $1
WHERE user_id = $2
`, [mainUserId, secondaryUserId]);
`,
[mainUserId, secondaryUserId]
);
// Переносим другие связанные данные...
// ...
@@ -227,10 +243,10 @@ async function main() {
const normalizedAddress = normalizeWalletAddress(wallet.provider_id);
if (normalizedAddress !== wallet.provider_id) {
await client.query(
'UPDATE user_identities SET provider_id = $1 WHERE id = $2',
[normalizedAddress, wallet.id]
);
await client.query('UPDATE user_identities SET provider_id = $1 WHERE id = $2', [
normalizedAddress,
wallet.id,
]);
updatedCount++;
}
} catch (error) {

View File

@@ -19,7 +19,7 @@ async function runMigrations() {
// Получаем список выполненных миграций
const { rows } = await pool.query('SELECT name FROM migrations');
const executedMigrations = new Set(rows.map(row => row.name));
const executedMigrations = new Set(rows.map((row) => row.name));
// Читаем файлы миграций
const migrationsDir = path.join(__dirname, '../db/migrations');
@@ -27,7 +27,7 @@ async function runMigrations() {
// Сортируем файлы по номеру
const migrationFiles = files
.filter(f => f.endsWith('.sql'))
.filter((f) => f.endsWith('.sql'))
.sort((a, b) => {
const numA = parseInt(a.split('_')[0]);
const numB = parseInt(b.split('_')[0]);
@@ -55,7 +55,12 @@ async function runMigrations() {
// Выполняем SQL-функции
const functionsDir = path.join(migrationsDir, 'functions');
if (await fs.stat(functionsDir).then(() => true).catch(() => false)) {
if (
await fs
.stat(functionsDir)
.then(() => true)
.catch(() => false)
) {
const functionFiles = await fs.readdir(functionsDir);
for (const file of functionFiles) {

View File

@@ -46,7 +46,9 @@ async function initServices() {
});
} catch (error) {
if (error.code === 409) {
logger.warn('Another instance of Telegram bot is running. This is normal during development with nodemon');
logger.warn(
'Another instance of Telegram bot is running. This is normal during development with nodemon'
);
// Просто логируем ошибку и продолжаем работу
// Бот будет запущен при следующем перезапуске
} else {
@@ -61,20 +63,22 @@ async function initServices() {
}
// Настройка сессий
app.use(session({
store: new pgSession({
pool: pool,
tableName: 'session'
}),
secret: process.env.SESSION_SECRET || 'hb3atoken',
resave: false,
saveUninitialized: true,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 30 * 24 * 60 * 60 * 1000 // 30 дней
}
}));
app.use(
session({
store: new pgSession({
pool: pool,
tableName: 'session',
}),
secret: process.env.SESSION_SECRET || 'hb3atoken',
resave: false,
saveUninitialized: true,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 дней
},
})
);
// Маршруты API
app.use('/api/users', usersRouter);

View File

@@ -12,9 +12,10 @@ class AIAssistant {
// Создание экземпляра ChatOllama с нужными параметрами
createChat(language = 'ru') {
const systemPrompt = language === 'ru'
? 'Вы - полезный ассистент. Отвечайте на русском языке.'
: 'You are a helpful assistant. Respond in English.';
const systemPrompt =
language === 'ru'
? 'Вы - полезный ассистент. Отвечайте на русском языке.'
: 'You are a helpful assistant. Respond in English.';
return new ChatOllama({
baseUrl: this.baseUrl,
@@ -22,7 +23,7 @@ class AIAssistant {
system: systemPrompt,
temperature: 0.7,
maxTokens: 1000,
timeout: 30000 // 30 секунд таймаут
timeout: 30000, // 30 секунд таймаут
});
}
@@ -38,9 +39,7 @@ class AIAssistant {
console.log('getResponse called with:', { message, language });
// Определяем язык, если не указан явно
const detectedLanguage = language === 'auto'
? this.detectLanguage(message)
: language;
const detectedLanguage = language === 'auto' ? this.detectLanguage(message) : language;
console.log('Detected language:', detectedLanguage);
@@ -67,7 +66,7 @@ class AIAssistant {
}
} catch (error) {
console.error('Error in getResponse:', error);
return "Извините, я не смог обработать ваш запрос. Пожалуйста, попробуйте позже.";
return 'Извините, я не смог обработать ваш запрос. Пожалуйста, попробуйте позже.';
}
}
@@ -76,9 +75,10 @@ class AIAssistant {
try {
console.log('Using fallback request method with:', { message, language });
const systemPrompt = language === 'ru'
? 'Вы - полезный ассистент. Отвечайте на русском языке.'
: 'You are a helpful assistant. Respond in English.';
const systemPrompt =
language === 'ru'
? 'Вы - полезный ассистент. Отвечайте на русском языке.'
: 'You are a helpful assistant. Respond in English.';
console.log('Sending request to Ollama API...');
const response = await fetch(`${this.baseUrl}/api/generate`, {
@@ -91,8 +91,8 @@ class AIAssistant {
stream: false,
options: {
temperature: 0.7,
num_predict: 1000
}
num_predict: 1000,
},
}),
});

View File

@@ -6,15 +6,13 @@ const { processMessage } = require('./ai-assistant'); // Используем AI
const verificationService = require('./verification-service'); // Используем сервис верификации
const ADMIN_CONTRACTS = [
{ address: "0xd95a45fc46a7300e6022885afec3d618d7d3f27c", network: "eth" },
{ address: "0x4B294265720B09ca39BFBA18c7E368413c0f68eB", network: "bsc" },
{ address: "0xdce769b847a0a697239777d0b1c7dd33b6012ba0", network: "arbitrum" },
{ address: "0x351f59de4fedbdf7601f5592b93db3b9330c1c1d", network: "polygon" }
{ address: '0xd95a45fc46a7300e6022885afec3d618d7d3f27c', network: 'eth' },
{ address: '0x4B294265720B09ca39BFBA18c7E368413c0f68eB', network: 'bsc' },
{ address: '0xdce769b847a0a697239777d0b1c7dd33b6012ba0', network: 'arbitrum' },
{ address: '0x351f59de4fedbdf7601f5592b93db3b9330c1c1d', network: 'polygon' },
];
const ERC20_ABI = [
"function balanceOf(address owner) view returns (uint256)"
];
const ERC20_ABI = ['function balanceOf(address owner) view returns (uint256)'];
class AuthService {
constructor() {
@@ -22,7 +20,7 @@ class AuthService {
eth: new ethers.JsonRpcProvider(process.env.RPC_URL_ETH),
polygon: new ethers.JsonRpcProvider(process.env.RPC_URL_POLYGON),
bsc: new ethers.JsonRpcProvider(process.env.RPC_URL_BSC),
arbitrum: new ethers.JsonRpcProvider(process.env.RPC_URL_ARBITRUM)
arbitrum: new ethers.JsonRpcProvider(process.env.RPC_URL_ARBITRUM),
};
}
@@ -56,11 +54,14 @@ class AuthService {
const normalizedAddress = ethers.getAddress(address).toLowerCase();
// Ищем пользователя по адресу в таблице user_identities
const userResult = await db.query(`
const userResult = await db.query(
`
SELECT u.* FROM users u
JOIN user_identities ui ON u.id = ui.user_id
WHERE ui.provider = 'wallet' AND ui.provider_id = $1
`, [normalizedAddress]);
`,
[normalizedAddress]
);
if (userResult.rows.length > 0) {
const user = userResult.rows[0];
@@ -81,15 +82,14 @@ class AuthService {
return {
userId: user.id,
isAdmin: user.role === 'admin'
isAdmin: user.role === 'admin',
};
}
// Если пользователь не найден, создаем нового
const newUserResult = await db.query(
'INSERT INTO users (role) VALUES ($1) RETURNING id',
['user']
);
const newUserResult = await db.query('INSERT INTO users (role) VALUES ($1) RETURNING id', [
'user',
]);
const userId = newUserResult.rows[0].id;
@@ -106,7 +106,9 @@ class AuthService {
// Если у пользователя есть админские токены, обновляем его роль
if (isAdmin) {
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
logger.info(`New user ${userId} with wallet ${normalizedAddress} automatically granted admin role`);
logger.info(
`New user ${userId} with wallet ${normalizedAddress} automatically granted admin role`
);
}
return { userId, isAdmin };
@@ -151,17 +153,15 @@ class AuthService {
await Promise.race([networkCheckPromise, timeoutPromise]);
} catch (networkError) {
logger.error(`Provider for ${contract.network} is not available: ${networkError.message}`);
logger.error(
`Provider for ${contract.network} is not available: ${networkError.message}`
);
balances[contract.network] = 'Error: Network unavailable';
errorCount++;
return null;
}
const tokenContract = new ethers.Contract(
contract.address,
ERC20_ABI,
provider
);
const tokenContract = new ethers.Contract(contract.address, ERC20_ABI, provider);
// Создаем промис с таймаутом
const balancePromise = tokenContract.balanceOf(address);
@@ -178,7 +178,7 @@ class AuthService {
address,
contract: contract.address,
balance: formattedBalance,
hasTokens: balance > 0
hasTokens: balance > 0,
});
if (parseFloat(formattedBalance) > 0) {
@@ -191,7 +191,7 @@ class AuthService {
logger.error(`Error checking balance in ${contract.network}:`, {
address,
contract: contract.address,
error: error.message || 'Unknown error'
error: error.message || 'Unknown error',
});
balances[contract.network] = 'Error';
errorCount++;
@@ -210,8 +210,10 @@ class AuthService {
if (foundTokens) {
logger.info(`Admin role summary for ${address}:`, {
networks: Object.keys(balances).filter(net => balances[net] > 0 && balances[net] !== 'Error'),
balances
networks: Object.keys(balances).filter(
(net) => balances[net] > 0 && balances[net] !== 'Error'
),
balances,
});
logger.info(`Admin role granted for ${address}`);
return true;
@@ -233,7 +235,7 @@ class AuthService {
eth: '0',
bsc: '0',
arbitrum: '0',
polygon: '0'
polygon: '0',
};
}
@@ -259,16 +261,14 @@ class AuthService {
await Promise.race([networkCheckPromise, networkTimeoutPromise]);
} catch (networkError) {
logger.error(`Provider for ${contract.network} is not available: ${networkError.message}`);
logger.error(
`Provider for ${contract.network} is not available: ${networkError.message}`
);
balances[contract.network] = '0';
continue;
}
const tokenContract = new ethers.Contract(
contract.address,
ERC20_ABI,
provider
);
const tokenContract = new ethers.Contract(contract.address, ERC20_ABI, provider);
// Создаем промис с таймаутом
const balancePromise = tokenContract.balanceOf(address);
@@ -283,7 +283,7 @@ class AuthService {
logger.info(`Token balance for ${address} on ${contract.network}:`, {
contract: contract.address,
balance: formattedBalance,
timestamp: new Date().toISOString()
timestamp: new Date().toISOString(),
});
balances[contract.network] = formattedBalance;
@@ -292,7 +292,7 @@ class AuthService {
address,
contract: contract.address,
error: error.message || 'Unknown error',
timestamp: new Date().toISOString()
timestamp: new Date().toISOString(),
});
balances[contract.network] = '0';
}
@@ -300,7 +300,7 @@ class AuthService {
logger.info(`Token balances fetched for ${address}:`, {
...balances,
timestamp: new Date().toISOString()
timestamp: new Date().toISOString(),
});
return balances;
@@ -329,13 +329,16 @@ class AuthService {
`UPDATE session
SET sess = $1
WHERE sid = $2`,
[JSON.stringify({
userId,
authenticated,
authType,
address,
cookie: session.cookie
}), session.id]
[
JSON.stringify({
userId,
authenticated,
authType,
address,
cookie: session.cookie,
}),
session.id,
]
);
return true;
@@ -410,7 +413,9 @@ class AuthService {
// Если есть кошелек, проверяем админские токены
const isAdmin = await this.checkAdminRole(wallet);
logger.info(`Role check for user ${userId} with wallet ${wallet}: ${isAdmin ? 'admin' : 'user'}`);
logger.info(
`Role check for user ${userId} with wallet ${wallet}: ${isAdmin ? 'admin' : 'user'}`
);
return isAdmin ? 'admin' : 'user';
} catch (error) {
logger.error('Error checking user role:', error);
@@ -432,10 +437,7 @@ class AuthService {
const email = result.providerId;
// Проверяем, существует ли пользователь с таким email
const userResult = await db.query(
'SELECT * FROM users WHERE id = $1',
[userId]
);
const userResult = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
if (userResult.rows.length === 0) {
return { verified: false };
@@ -459,7 +461,7 @@ class AuthService {
userId,
email,
role,
wallet: wallet || null
wallet: wallet || null,
};
} catch (error) {
logger.error('Error checking email verification:', error);
@@ -481,7 +483,9 @@ class AuthService {
if (session && session.authenticated && session.userId) {
// Если есть авторизованный пользователь в сессии, связываем Telegram с ним
userId = session.userId;
logger.info(`[verifyTelegramAuth] Using existing authenticated user ${userId} from session`);
logger.info(
`[verifyTelegramAuth] Using existing authenticated user ${userId} from session`
);
// Связываем Telegram с текущим пользователем
await this.linkIdentity(userId, 'telegram', telegramId);
@@ -491,7 +495,7 @@ class AuthService {
userId,
role: session.isAdmin ? 'admin' : 'user',
telegramId,
isNewUser: false
isNewUser: false,
};
}
@@ -509,13 +513,14 @@ class AuthService {
if (existingUserResult.rows.length > 0) {
const existingUser = existingUserResult.rows[0];
userId = existingUser.id;
logger.info(`[verifyTelegramAuth] Found existing user ${userId} for Telegram ID ${telegramId}`);
logger.info(
`[verifyTelegramAuth] Found existing user ${userId} for Telegram ID ${telegramId}`
);
} else {
// Создаем нового пользователя для нового telegramId
const newUserResult = await db.query(
'INSERT INTO users (role) VALUES ($1) RETURNING id',
['user']
);
const newUserResult = await db.query('INSERT INTO users (role) VALUES ($1) RETURNING id', [
'user',
]);
userId = newUserResult.rows[0].id;
isNewUser = true;
@@ -525,7 +530,9 @@ class AuthService {
[userId, 'telegram', telegramId]
);
logger.info(`[verifyTelegramAuth] Created new user ${userId} for Telegram ID ${telegramId}`);
logger.info(
`[verifyTelegramAuth] Created new user ${userId} for Telegram ID ${telegramId}`
);
}
// Если есть гостевой ID в сессии, сохраняем его для нового пользователя
@@ -542,7 +549,7 @@ class AuthService {
userId,
role: 'user',
telegramId,
isNewUser
isNewUser,
};
} catch (error) {
logger.error('[verifyTelegramAuth] Error:', error);
@@ -563,7 +570,8 @@ class AuthService {
if (isAdmin) {
try {
// Находим userId по адресу
const userResult = await db.query(`
const userResult = await db.query(
`
SELECT u.id FROM users u
JOIN user_identities ui ON u.id = ui.user_id
WHERE ui.provider = 'wallet' AND ui.provider_id = $1`,
@@ -573,10 +581,7 @@ class AuthService {
if (userResult.rows.length > 0) {
const userId = userResult.rows[0].id;
// Обновляем роль пользователя
await db.query(
'UPDATE users SET role = $1 WHERE id = $2',
['admin', userId]
);
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
logger.info(`Updated user ${userId} role to admin based on token holdings`);
}
} catch (error) {
@@ -586,7 +591,8 @@ class AuthService {
} else {
// Если пользователь не является администратором, сбрасываем роль на "user", если она была "admin"
try {
const userResult = await db.query(`
const userResult = await db.query(
`
SELECT u.id, u.role FROM users u
JOIN user_identities ui ON u.id = ui.user_id
WHERE ui.provider = 'wallet' AND ui.provider_id = $1`,
@@ -595,10 +601,7 @@ class AuthService {
if (userResult.rows.length > 0 && userResult.rows[0].role === 'admin') {
const userId = userResult.rows[0].id;
await db.query(
'UPDATE users SET role = $1 WHERE id = $2',
['user', userId]
);
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['user', userId]);
logger.info(`Reset user ${userId} role from admin to user (no tokens found)`);
}
} catch (error) {
@@ -624,7 +627,7 @@ class AuthService {
const identities = await this.getUserIdentities(userId);
// Фильтруем только гостевые идентификаторы
const guestIdentities = identities.filter(id => id.identity_type === 'guest');
const guestIdentities = identities.filter((id) => id.identity_type === 'guest');
// Если гостевых идентификаторов больше 3, удаляем старые
if (guestIdentities.length > 3) {
@@ -636,10 +639,7 @@ class AuthService {
// Удаляем старые идентификаторы
for (const identity of identitiesToDelete) {
await db.query(
'DELETE FROM user_identities WHERE id = $1',
[identity.id]
);
await db.query('DELETE FROM user_identities WHERE id = $1', [identity.id]);
logger.info(`Deleted old guest identity: ${identity.identity_value}`);
}
}
@@ -676,9 +676,7 @@ class AuthService {
try {
const balance = await Promise.race([
this.getTokenBalance(address, ADMIN_CONTRACTS.ARBITRUM),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('TIMEOUT')), timeout)
)
new Promise((_, reject) => setTimeout(() => reject(new Error('TIMEOUT')), timeout)),
]);
return { balance, hasTokens: balance > 0 };
} catch (error) {
@@ -697,7 +695,9 @@ class AuthService {
async linkIdentity(userId, provider, providerId) {
try {
if (!userId || !provider || !providerId) {
logger.warn(`[AuthService] Missing parameters for linkIdentity: userId=${userId}, provider=${provider}, providerId=${providerId}`);
logger.warn(
`[AuthService] Missing parameters for linkIdentity: userId=${userId}, provider=${provider}, providerId=${providerId}`
);
throw new Error('Missing parameters');
}
@@ -715,7 +715,9 @@ class AuthService {
normalizedProviderId = providerId.toLowerCase();
}
logger.info(`[AuthService] Linking identity ${provider}:${normalizedProviderId} to user ${userId}`);
logger.info(
`[AuthService] Linking identity ${provider}:${normalizedProviderId} to user ${userId}`
);
// Проверяем, существует ли уже такой идентификатор
const existingResult = await db.query(
@@ -728,11 +730,15 @@ class AuthService {
// Если идентификатор уже принадлежит этому пользователю, ничего не делаем
if (existingUserId === userId) {
logger.info(`[AuthService] Identity ${provider}:${normalizedProviderId} already exists for user ${userId}`);
logger.info(
`[AuthService] Identity ${provider}:${normalizedProviderId} already exists for user ${userId}`
);
return { success: true, message: 'Identity already exists' };
} else {
// Если идентификатор принадлежит другому пользователю, возвращаем ошибку
logger.warn(`[AuthService] Identity ${provider}:${normalizedProviderId} already belongs to user ${existingUserId}, not user ${userId}`);
logger.warn(
`[AuthService] Identity ${provider}:${normalizedProviderId} already belongs to user ${existingUserId}, not user ${userId}`
);
throw new Error(`Identity already belongs to another user (${existingUserId})`);
}
}
@@ -751,76 +757,20 @@ class AuthService {
// Обновляем роль пользователя в базе данных, если нужно
if (isAdmin) {
await db.query(
'UPDATE users SET role = $1 WHERE id = $2',
['admin', userId]
);
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
logger.info(`[AuthService] Updated user ${userId} role to admin based on token holdings`);
}
}
logger.info(`[AuthService] Identity ${provider}:${normalizedProviderId} successfully linked to user ${userId}`);
logger.info(
`[AuthService] Identity ${provider}:${normalizedProviderId} successfully linked to user ${userId}`
);
return { success: true, isAdmin };
} catch (error) {
logger.error(`[AuthService] Error linking identity ${provider}:${providerId} to user ${userId}:`, error);
throw error;
}
}
/**
* Обработка гостевых сообщений после аутентификации
* ПРИМЕЧАНИЕ: Эта функция оставлена для обратной совместимости.
* Фактически все маршруты теперь используют версию функции из auth.js,
* которая корректно обрабатывает сообщения для всех типов аутентификации.
* @deprecated Используйте функцию linkGuestMessagesAfterAuth из routes/auth.js
*/
async linkGuestMessagesAfterAuth(userId, currentGuestId, previousGuestId) {
try {
logger.info(`[linkGuestMessagesAfterAuth] Starting for user ${userId} with guestId=${currentGuestId}`);
// Проверяем, есть ли идентификатор для обработки
if (!currentGuestId) {
logger.debug('[linkGuestMessagesAfterAuth] No guest ID to process');
return { success: true, message: 'No guest ID to process' };
}
// Проверяем, не привязаны ли уже эти гостевые сообщения к другому пользователю
const existingMessagesCheck = await db.query(
`SELECT DISTINCT user_id
FROM messages
WHERE guest_message_id IN (
SELECT id FROM guest_messages WHERE guest_id = $1
)`,
[currentGuestId]
logger.error(
`[AuthService] Error linking identity ${provider}:${providerId} to user ${userId}:`,
error
);
if (existingMessagesCheck.rows.length > 0) {
const existingUserId = existingMessagesCheck.rows[0].user_id;
if (existingUserId !== userId) {
logger.warn(`[linkGuestMessagesAfterAuth] Guest messages for ${currentGuestId} are already linked to user ${existingUserId}`);
return {
success: false,
error: 'Guest messages are already linked to another user'
};
}
}
// Используем ту же функцию processGuestMessages что и в auth.js
const result = await processGuestMessages(userId, currentGuestId);
logger.info(`[linkGuestMessagesAfterAuth] Guest messages processed: ${JSON.stringify(result)}`);
// Если есть предыдущий гостевой ID, обработаем и его
if (previousGuestId && previousGuestId !== currentGuestId) {
const prevResult = await processGuestMessages(userId, previousGuestId);
logger.info(`[linkGuestMessagesAfterAuth] Previous guest messages processed: ${JSON.stringify(prevResult)}`);
}
return {
success: true,
result: result
};
} catch (error) {
logger.error('[linkGuestMessagesAfterAuth] Error:', error);
throw error;
}
}

View File

@@ -30,17 +30,18 @@ class EmailAuth {
if (session.authenticated && session.userId) {
// Если пользователь уже аутентифицирован, используем его ID
userId = session.userId;
logger.info(`[initEmailAuth] Using existing authenticated user ${userId} for email ${email}`);
logger.info(
`[initEmailAuth] Using existing authenticated user ${userId} for email ${email}`
);
} else if (existingEmailUser.rows.length > 0) {
// Если найден пользователь с таким email, используем его ID
userId = existingEmailUser.rows[0].id;
logger.info(`[initEmailAuth] Found existing user ${userId} with email ${email}`);
} else {
// Создаем временного пользователя, если нужно будет создать нового
const userResult = await db.query(
'INSERT INTO users (role) VALUES ($1) RETURNING id',
['user']
);
const userResult = await db.query('INSERT INTO users (role) VALUES ($1) RETURNING id', [
'user',
]);
userId = userResult.rows[0].id;
session.tempUserId = userId;
logger.info(`[initEmailAuth] Created temporary user ${userId} for email ${email}`);
@@ -59,7 +60,9 @@ class EmailAuth {
// Отправляем код на email
await this.emailBot.sendVerificationCode(email, verificationCode);
logger.info(`Generated verification code for Email auth for ${email} and sent to user's email`);
logger.info(
`Generated verification code for Email auth for ${email} and sent to user's email`
);
return { success: true, verificationCode };
} catch (error) {
@@ -103,18 +106,21 @@ class EmailAuth {
return {
verified: true,
userId: finalUserId,
email: email
email: email,
};
}
// Если пользователь не авторизован, ищем всех пользователей с похожими идентификаторами
const identities = {
email: email,
guest: session.guestId
guest: session.guestId,
};
const relatedUsers = await authService.identityService.findRelatedUsers(identities);
logger.info(`[checkEmailVerification] Found ${relatedUsers.length} related users for identities:`, identities);
logger.info(
`[checkEmailVerification] Found ${relatedUsers.length} related users for identities:`,
identities
);
if (relatedUsers.length > 0) {
// Берем первого найденного пользователя как основного
@@ -124,13 +130,17 @@ class EmailAuth {
// Мигрируем данные от остальных пользователей к основному
for (const userId of relatedUsers.slice(1)) {
await authService.identityService.migrateUserData(userId, finalUserId);
logger.info(`[checkEmailVerification] Migrated data from user ${userId} to ${finalUserId}`);
logger.info(
`[checkEmailVerification] Migrated data from user ${userId} to ${finalUserId}`
);
}
// Если у нас есть временный пользователь, мигрируем его данные тоже
if (session.tempUserId && !relatedUsers.includes(session.tempUserId)) {
await authService.identityService.migrateUserData(session.tempUserId, finalUserId);
logger.info(`[checkEmailVerification] Migrated temporary user ${session.tempUserId} to ${finalUserId}`);
logger.info(
`[checkEmailVerification] Migrated temporary user ${session.tempUserId} to ${finalUserId}`
);
}
} else {
// Если связанных пользователей нет, используем временного или создаем нового
@@ -154,7 +164,9 @@ class EmailAuth {
// Если есть гостевой ID, добавляем его тоже
if (session.guestId) {
await authService.identityService.saveIdentity(finalUserId, 'guest', session.guestId, true);
logger.info(`[checkEmailVerification] Added guest identity ${session.guestId} for user ${finalUserId}`);
logger.info(
`[checkEmailVerification] Added guest identity ${session.guestId} for user ${finalUserId}`
);
}
// Очищаем временные данные
@@ -166,7 +178,7 @@ class EmailAuth {
return {
verified: true,
userId: finalUserId,
email: email
email: email,
};
} catch (error) {
logger.error('Error checking email verification:', error);

View File

@@ -19,8 +19,8 @@ const transporter = nodemailer.createTransport({
maxConnections: 3,
maxMessages: 5,
tls: {
rejectUnauthorized: false
}
rejectUnauthorized: false,
},
});
// Конфигурация для получения писем
@@ -34,8 +34,8 @@ const imapConfig = {
keepalive: {
interval: 10000,
idleInterval: 300000,
forceNoop: true
}
forceNoop: true,
},
};
class EmailBotService {
@@ -91,7 +91,7 @@ class EmailBotService {
</div>
<p style="font-size: 14px; color: #999;">Код действителен в течение 15 минут.</p>
</div>
`
`,
};
await this.transporter.sendMail(mailOptions);
@@ -191,7 +191,7 @@ class EmailBotService {
from: process.env.EMAIL_USER,
to,
subject,
text
text,
};
await this.transporter.sendMail(mailOptions);

View File

@@ -27,7 +27,7 @@ class IdentityService {
return {
provider: normalizedProvider,
providerId: normalizedProviderId
providerId: normalizedProviderId,
};
}
@@ -42,10 +42,12 @@ class IdentityService {
async saveIdentity(userId, provider, providerId, verified = true) {
try {
if (!userId || !provider || !providerId) {
logger.warn(`[IdentityService] Missing required parameters: userId=${userId}, provider=${provider}, providerId=${providerId}`);
logger.warn(
`[IdentityService] Missing required parameters: userId=${userId}, provider=${provider}, providerId=${providerId}`
);
return {
success: false,
error: 'Missing required parameters'
error: 'Missing required parameters',
};
}
@@ -55,7 +57,9 @@ class IdentityService {
// Проверяем тип провайдера и перенаправляем гостевые идентификаторы в guest_user_mapping
if (normalizedProvider === 'guest') {
logger.info(`[IdentityService] Converting guest identity for user ${userId} to guest_user_mapping: ${normalizedProviderId}`);
logger.info(
`[IdentityService] Converting guest identity for user ${userId} to guest_user_mapping: ${normalizedProviderId}`
);
try {
await db.query(
@@ -64,7 +68,10 @@ class IdentityService {
);
return { success: true };
} catch (guestError) {
logger.error(`[IdentityService] Error saving guest identity for user ${userId}:`, guestError);
logger.error(
`[IdentityService] Error saving guest identity for user ${userId}:`,
guestError
);
return { success: false, error: guestError.message };
}
}
@@ -75,11 +82,13 @@ class IdentityService {
logger.warn(`[IdentityService] Invalid provider type: ${normalizedProvider}`);
return {
success: false,
error: `Invalid provider type. Allowed types: ${allowedProviders.join(', ')}`
error: `Invalid provider type. Allowed types: ${allowedProviders.join(', ')}`,
};
}
logger.info(`[IdentityService] Saving identity for user ${userId}: ${normalizedProvider}:${normalizedProviderId}`);
logger.info(
`[IdentityService] Saving identity for user ${userId}: ${normalizedProvider}:${normalizedProviderId}`
);
// Проверяем, существует ли уже такой идентификатор
const existingResult = await db.query(
@@ -92,13 +101,17 @@ class IdentityService {
// Если идентификатор уже принадлежит этому пользователю, ничего не делаем
if (existingUserId === userId) {
logger.info(`[IdentityService] Identity ${normalizedProvider}:${normalizedProviderId} already exists for user ${userId}`);
logger.info(
`[IdentityService] Identity ${normalizedProvider}:${normalizedProviderId} already exists for user ${userId}`
);
} else {
// Если идентификатор принадлежит другому пользователю, логируем это
logger.warn(`[IdentityService] Identity ${normalizedProvider}:${normalizedProviderId} already belongs to user ${existingUserId}, not user ${userId}`);
logger.warn(
`[IdentityService] Identity ${normalizedProvider}:${normalizedProviderId} already belongs to user ${existingUserId}, not user ${userId}`
);
return {
success: false,
error: `Identity already belongs to another user (${existingUserId})`
error: `Identity already belongs to another user (${existingUserId})`,
};
}
} else {
@@ -108,12 +121,17 @@ class IdentityService {
VALUES ($1, $2, $3)`,
[userId, normalizedProvider, normalizedProviderId]
);
logger.info(`[IdentityService] Created new identity ${normalizedProvider}:${normalizedProviderId} for user ${userId}`);
logger.info(
`[IdentityService] Created new identity ${normalizedProvider}:${normalizedProviderId} for user ${userId}`
);
}
return { success: true };
} catch (error) {
logger.error(`[IdentityService] Error saving identity ${provider}:${providerId} for user ${userId}:`, error);
logger.error(
`[IdentityService] Error saving identity ${provider}:${providerId} for user ${userId}:`,
error
);
return { success: false, error: error.message };
}
}
@@ -161,10 +179,15 @@ class IdentityService {
[userId, provider]
);
logger.info(`[IdentityService] Found ${result.rows.length} ${provider} identities for user ${userId}`);
return result.rows.map(row => row.provider_id);
logger.info(
`[IdentityService] Found ${result.rows.length} ${provider} identities for user ${userId}`
);
return result.rows.map((row) => row.provider_id);
} catch (error) {
logger.error(`[IdentityService] Error getting ${provider} identities for user ${userId}:`, error);
logger.error(
`[IdentityService] Error getting ${provider} identities for user ${userId}:`,
error
);
return [];
}
}
@@ -178,7 +201,9 @@ class IdentityService {
async findUserByIdentity(provider, providerId) {
try {
if (!provider || !providerId) {
logger.warn(`[IdentityService] Missing parameters: provider=${provider}, providerId=${providerId}`);
logger.warn(
`[IdentityService] Missing parameters: provider=${provider}, providerId=${providerId}`
);
return null;
}
@@ -194,14 +219,21 @@ class IdentityService {
);
if (result.rows.length === 0) {
logger.info(`[IdentityService] No user found with identity ${normalizedProvider}:${normalizedProviderId}`);
logger.info(
`[IdentityService] No user found with identity ${normalizedProvider}:${normalizedProviderId}`
);
return null;
}
logger.info(`[IdentityService] Found user ${result.rows[0].id} with identity ${normalizedProvider}:${normalizedProviderId}`);
logger.info(
`[IdentityService] Found user ${result.rows[0].id} with identity ${normalizedProvider}:${normalizedProviderId}`
);
return result.rows[0];
} catch (error) {
logger.error(`[IdentityService] Error finding user by identity ${provider}:${providerId}:`, error);
logger.error(
`[IdentityService] Error finding user by identity ${provider}:${providerId}:`,
error
);
return null;
}
}
@@ -233,7 +265,12 @@ class IdentityService {
}
if (session.telegramId) {
const telegramResult = await this.saveIdentity(userId, 'telegram', session.telegramId, true);
const telegramResult = await this.saveIdentity(
userId,
'telegram',
session.telegramId,
true
);
results.push({ type: 'telegram', result: telegramResult });
}
@@ -259,15 +296,23 @@ class IdentityService {
);
results.push({ type: 'previousGuest', result: { success: true } });
} catch (error) {
logger.error(`[IdentityService] Error saving previous guest ID for user ${userId}:`, error);
logger.error(
`[IdentityService] Error saving previous guest ID for user ${userId}:`,
error
);
results.push({ type: 'previousGuest', result: { success: false, error: error.message } });
}
}
logger.info(`[IdentityService] Saved ${results.length} identities from session for user ${userId}`);
logger.info(
`[IdentityService] Saved ${results.length} identities from session for user ${userId}`
);
return { success: true, results };
} catch (error) {
logger.error(`[IdentityService] Error saving identities from session for user ${userId}:`, error);
logger.error(
`[IdentityService] Error saving identities from session for user ${userId}:`,
error
);
return { success: false, error: error.message };
}
}
@@ -281,7 +326,9 @@ class IdentityService {
async migrateUserData(fromUserId, toUserId) {
try {
if (!fromUserId || !toUserId) {
logger.warn(`[IdentityService] Missing parameters: fromUserId=${fromUserId}, toUserId=${toUserId}`);
logger.warn(
`[IdentityService] Missing parameters: fromUserId=${fromUserId}, toUserId=${toUserId}`
);
return { success: false, error: 'Missing required parameters' };
}
@@ -331,10 +378,7 @@ class IdentityService {
}
// Удаляем старые гостевые маппинги
await client.query(
`DELETE FROM guest_user_mapping WHERE user_id = $1`,
[fromUserId]
);
await client.query(`DELETE FROM guest_user_mapping WHERE user_id = $1`, [fromUserId]);
// Переносим все сообщения
await client.query(
@@ -363,7 +407,9 @@ class IdentityService {
// Завершаем транзакцию
await client.query('COMMIT');
logger.info(`[IdentityService] Successfully migrated data from user ${fromUserId} to ${toUserId}`);
logger.info(
`[IdentityService] Successfully migrated data from user ${fromUserId} to ${toUserId}`
);
return { success: true };
} catch (error) {
await client.query('ROLLBACK');
@@ -397,7 +443,7 @@ class IdentityService {
[provider, providerId]
);
result.rows.forEach(row => userIds.add(row.user_id));
result.rows.forEach((row) => userIds.add(row.user_id));
}
return Array.from(userIds);

View File

@@ -31,5 +31,5 @@ module.exports = {
getConversationHistory: aiAssistant.getConversationHistory,
telegramBot,
aiAssistant
aiAssistant,
};

View File

@@ -14,7 +14,7 @@ class SessionService {
async saveSession(session) {
try {
return new Promise((resolve, reject) => {
session.save(err => {
session.save((err) => {
if (err) {
logger.error('Error saving session:', err);
reject(err);
@@ -38,7 +38,9 @@ class SessionService {
*/
async linkGuestMessages(session, userId) {
try {
logger.info(`[linkGuestMessages] Starting for user ${userId} with guestId=${session.guestId}, previousGuestId=${session.previousGuestId}`);
logger.info(
`[linkGuestMessages] Starting for user ${userId} with guestId=${session.guestId}, previousGuestId=${session.previousGuestId}`
);
// Инициализируем массив обработанных гостевых ID, если его нет
if (!session.processedGuestIds) {
@@ -50,7 +52,7 @@ class SessionService {
'SELECT guest_id FROM guest_user_mapping WHERE user_id = $1',
[userId]
);
const userGuestIds = guestIdsResult.rows.map(row => row.guest_id);
const userGuestIds = guestIdsResult.rows.map((row) => row.guest_id);
// Собираем все гостевые ID, которые нужно обработать
const guestIdsToProcess = new Set();
@@ -90,10 +92,9 @@ class SessionService {
session.processedGuestIds.push(guestId);
// Помечаем guestId как обработанный в базе данных
await db.query(
'UPDATE guest_user_mapping SET processed = true WHERE guest_id = $1',
[guestId]
);
await db.query('UPDATE guest_user_mapping SET processed = true WHERE guest_id = $1', [
guestId,
]);
}
// Сохраняем сессию
@@ -114,7 +115,9 @@ class SessionService {
*/
async processGuestMessagesWrapper(userId, guestId) {
try {
logger.info(`[processGuestMessagesWrapper] Processing messages: userId=${userId}, guestId=${guestId}`);
logger.info(
`[processGuestMessagesWrapper] Processing messages: userId=${userId}, guestId=${guestId}`
);
return await processGuestMessages(userId, guestId);
} catch (error) {
logger.error(`[processGuestMessagesWrapper] Error: ${error.message}`, error);
@@ -146,7 +149,7 @@ class SessionService {
async destroySession(session) {
try {
return new Promise((resolve, reject) => {
session.destroy(err => {
session.destroy((err) => {
if (err) {
logger.error('Error destroying session:', err);
reject(err);
@@ -176,10 +179,7 @@ class SessionService {
logger.info(`[SessionService] Attempting to retrieve session ${sessionId}`);
const result = await db.query(
'SELECT sess FROM session WHERE sid = $1',
[sessionId]
);
const result = await db.query('SELECT sess FROM session WHERE sid = $1', [sessionId]);
if (result.rows.length === 0) {
logger.info(`[SessionService] No session found with ID ${sessionId}`);

View File

@@ -39,14 +39,13 @@ async function getBot() {
const verification = codeResult.rows[0];
const providerId = verification.provider_id;
const linkedUserId = verification.user_id; // Получаем связанный userId если он есть
const linkedUserId = verification.user_id; // Получаем связанный userId если он есть
let userId;
// Отмечаем код как использованный
await db.query(
'UPDATE verification_codes SET used = true WHERE id = $1',
[verification.id]
);
await db.query('UPDATE verification_codes SET used = true WHERE id = $1', [
verification.id,
]);
logger.info('Starting Telegram auth process for code:', code);
@@ -74,7 +73,9 @@ async function getBot() {
VALUES ($1, $2, $3, NOW())`,
[userId, 'telegram', ctx.from.id.toString()]
);
logger.info(`Linked Telegram account ${ctx.from.id} to pre-authenticated user ${userId}`);
logger.info(
`Linked Telegram account ${ctx.from.id} to pre-authenticated user ${userId}`
);
} else {
// Проверяем, есть ли пользователь, связанный с гостевым идентификатором
let existingUserWithGuestId = null;
@@ -85,7 +86,9 @@ async function getBot() {
);
if (guestUserResult.rows.length > 0) {
existingUserWithGuestId = guestUserResult.rows[0].user_id;
logger.info(`Found existing user ${existingUserWithGuestId} by guest ID ${providerId}`);
logger.info(
`Found existing user ${existingUserWithGuestId} by guest ID ${providerId}`
);
}
}
@@ -140,10 +143,10 @@ async function getBot() {
JSON.stringify({
userId: userId.toString(),
authenticated: true,
authType: "telegram",
telegramId: ctx.from.id.toString()
authType: 'telegram',
telegramId: ctx.from.id.toString(),
}),
JSON.stringify({guestId: providerId})
JSON.stringify({ guestId: providerId }),
]
);
@@ -156,7 +159,6 @@ async function getBot() {
} catch (error) {
logger.warn('Could not delete code message:', error);
}
} catch (error) {
logger.error('Error in Telegram auth:', error);
await ctx.reply('Произошла ошибка при аутентификации. Попробуйте позже.');
@@ -204,7 +206,9 @@ async function initTelegramAuth(session) {
[session.userId, guestId]
);
logger.info(`[initTelegramAuth] Linked guestId ${guestId} to authenticated user ${session.userId}`);
logger.info(
`[initTelegramAuth] Linked guestId ${guestId} to authenticated user ${session.userId}`
);
}
// Создаем код через сервис верификации с идентификатором
@@ -214,11 +218,13 @@ async function initTelegramAuth(session) {
session.authenticated ? session.userId : null
);
logger.info(`[initTelegramAuth] Created verification code for guestId: ${session.guestId || tempId}${session.authenticated ? `, userId: ${session.userId}` : ''}`);
logger.info(
`[initTelegramAuth] Created verification code for guestId: ${session.guestId || tempId}${session.authenticated ? `, userId: ${session.userId}` : ''}`
);
return {
verificationCode: code,
botLink: `https://t.me/${process.env.TELEGRAM_BOT_USERNAME}`
botLink: `https://t.me/${process.env.TELEGRAM_BOT_USERNAME}`,
};
} catch (error) {
logger.error('Error initializing Telegram auth:', error);
@@ -229,5 +235,5 @@ async function initTelegramAuth(session) {
module.exports = {
getBot,
stopBot,
initTelegramAuth
initTelegramAuth,
};

View File

@@ -9,7 +9,10 @@ class VerificationService {
// Генерация кода
generateCode() {
const code = Math.random().toString(36).substring(2, 2 + this.codeLength).toUpperCase();
const code = Math.random()
.toString(36)
.substring(2, 2 + this.codeLength)
.toUpperCase();
logger.info(`Generated verification code: ${code}`);
return code;
}
@@ -20,7 +23,9 @@ class VerificationService {
const expiresAt = new Date(Date.now() + this.expirationMinutes * 60 * 1000);
try {
logger.info(`Creating verification code for ${provider}:${providerId}, userId: ${userId || 'null'}`);
logger.info(
`Creating verification code for ${provider}:${providerId}, userId: ${userId || 'null'}`
);
// Если userId не указан, добавляем запись без ссылки на пользователя
if (userId === null || userId === undefined) {
@@ -46,7 +51,7 @@ class VerificationService {
error: error.message,
provider,
providerId,
userId
userId,
});
throw error;
}
@@ -72,7 +77,9 @@ class VerificationService {
);
if (checkResult.rows.length > 0) {
logger.info(`Found codes for ${provider}:${providerId}: ${JSON.stringify(checkResult.rows.map(r => r.code))}`);
logger.info(
`Found codes for ${provider}:${providerId}: ${JSON.stringify(checkResult.rows.map((r) => r.code))}`
);
} else {
logger.warn(`No active codes found for ${provider}:${providerId}`);
}
@@ -88,30 +95,29 @@ class VerificationService {
);
if (result.rows.length === 0) {
logger.warn(`Invalid or expired code for ${provider}:${providerId}. Input: ${normalizedCode}`);
logger.warn(
`Invalid or expired code for ${provider}:${providerId}. Input: ${normalizedCode}`
);
return { success: false, error: 'Неверный или истекший код' };
}
const verification = result.rows[0];
// Отмечаем код как использованный
await db.query(
'UPDATE verification_codes SET used = true WHERE id = $1',
[verification.id]
);
await db.query('UPDATE verification_codes SET used = true WHERE id = $1', [verification.id]);
logger.info(`Code verified successfully for ${provider}:${providerId}`);
return {
success: true,
userId: verification.user_id,
providerId: verification.provider_id
providerId: verification.provider_id,
};
} catch (error) {
logger.error('Error verifying code:', {
error: error.message,
code,
provider,
providerId
providerId,
});
throw error;
}

View File

@@ -1,28 +1,28 @@
// Роли пользователей
const USER_ROLES = {
USER: 1,
ADMIN: 2
ADMIN: 2,
};
// Типы идентификаторов
const IDENTITY_TYPES = {
WALLET: 'wallet',
EMAIL: 'email',
TELEGRAM: 'telegram'
TELEGRAM: 'telegram',
};
// Каналы сообщений
const MESSAGE_CHANNELS = {
WEB: 'web',
TELEGRAM: 'telegram',
EMAIL: 'email'
EMAIL: 'email',
};
// Типы отправителей сообщений
const SENDER_TYPES = {
USER: 'user',
AI: 'ai',
ADMIN: 'admin'
ADMIN: 'admin',
};
// Коды ошибок
@@ -31,20 +31,20 @@ const ERROR_CODES = {
FORBIDDEN: 'forbidden',
NOT_FOUND: 'not_found',
INTERNAL_ERROR: 'internal_error',
BAD_REQUEST: 'bad_request'
BAD_REQUEST: 'bad_request',
};
// Настройки сессии
const SESSION_CONFIG = {
COOKIE_MAX_AGE: 24 * 60 * 60 * 1000, // 24 часа
COOKIE_SECURE: process.env.NODE_ENV === 'production',
COOKIE_SAME_SITE: 'lax'
COOKIE_SAME_SITE: 'lax',
};
// Настройки API
const API_CONFIG = {
RATE_LIMIT: 100, // запросов в минуту
TIMEOUT: 30000 // 30 секунд
TIMEOUT: 30000, // 30 секунд
};
module.exports = {
@@ -54,5 +54,5 @@ module.exports = {
SENDER_TYPES,
ERROR_CODES,
SESSION_CONFIG,
API_CONFIG
API_CONFIG,
};

View File

@@ -37,7 +37,8 @@ async function addUserIdentity(userId, provider, providerId) {
);
return true;
} catch (error) {
if (error.code === '23505') { // Уникальное ограничение нарушено
if (error.code === '23505') {
// Уникальное ограничение нарушено
return false;
}
throw error;
@@ -49,5 +50,5 @@ module.exports = {
isValidEmail,
generateVerificationCode,
checkUserIdentity,
addUserIdentity
addUserIdentity,
};

4936
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,8 @@
"preview": "vite preview",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --ignore-pattern 'node_modules/'",
"lint:fix": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-pattern 'node_modules/'",
"lint:style": "stylelint \"**/*.{vue,css}\"",
"lint:style:fix": "stylelint \"**/*.{vue,css}\" --fix",
"format": "prettier --write \"**/*.{js,vue,json,md}\"",
"format:check": "prettier --check \"**/*.{js,vue,json,md}\""
},
@@ -34,9 +36,13 @@
"eslint-plugin-prettier": "^5.2.3",
"eslint-plugin-vue": "^9.32.0",
"globals": "^16.0.0",
"postcss-html": "^1.8.0",
"prettier": "^3.5.3",
"rollup": "^3.29.4",
"rollup-plugin-polyfill-node": "^0.12.0",
"stylelint": "^14.16.1",
"stylelint-config-prettier": "^9.0.5",
"stylelint-config-standard-vue": "^1.0.0",
"vite": "^6.2.3"
}
}

View File

@@ -634,11 +634,17 @@
const loadMessages = async (options = {}) => {
const { silent = false, initial = false, authType = null } = options;
// Усиленная проверка для предотвращения параллельного выполнения
if (messageLoading.value.isInProgress) {
console.warn('[loadMessages] Выполнение уже идет, пропуск вызова.');
return;
}
messageLoading.value.isInProgress = true; // Устанавливаем флаг НЕМЕДЛЕННО
if (messageLoading.value.isLoadingMore && !initial) return;
try {
messageLoading.value.isLoadingMore = true;
messageLoading.value.isInProgress = true;
if (!silent) isLoading.value = true;
console.log(
@@ -732,8 +738,8 @@
removeFromStorage('guestId');
}
} else if (response.data.messages && response.data.messages.length) {
// Иначе добавляем к существующим
messages.value = [...messages.value, ...response.data.messages];
// Иначе добавляем к существующим (В НАЧАЛО МАССИВА)
messages.value = [...response.data.messages, ...messages.value];
}
console.log(`Загружено ${messages.value.length} сообщений из истории`);

View File

@@ -0,0 +1,15 @@
module.exports = {
extends: [
// Используем стандартную конфигурацию для Vue (включает CSS и <style> в .vue)
'stylelint-config-standard-vue',
// Отключает правила Stylelint, конфликтующие с Prettier
'stylelint-config-prettier',
],
// Здесь можно добавить или переопределить правила
rules: {
// Пример: можно отключить правило о пустой строке перед комментариями
// 'comment-empty-line-before': null,
// Пример: требовать нижний регистр для имен анимаций
// 'keyframes-name-pattern': '^[a-z][a-z0-9-]*$',
},
};

File diff suppressed because it is too large Load Diff