ваше сообщение коммита
This commit is contained in:
111
backend/app.js
111
backend/app.js
@@ -26,46 +26,47 @@ app.set('host', '0.0.0.0');
|
|||||||
app.set('port', process.env.PORT || 8000);
|
app.set('port', process.env.PORT || 8000);
|
||||||
|
|
||||||
// Настройка CORS
|
// Настройка CORS
|
||||||
app.use(cors({
|
app.use(
|
||||||
origin: [
|
cors({
|
||||||
'http://localhost:5173',
|
origin: [
|
||||||
'http://127.0.0.1:5173' // Добавляем альтернативный origin
|
'http://localhost:5173',
|
||||||
],
|
'http://127.0.0.1:5173', // Добавляем альтернативный origin
|
||||||
credentials: true,
|
],
|
||||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
credentials: true,
|
||||||
allowedHeaders: ['Content-Type', 'Authorization', 'Cookie']
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||||
}));
|
allowedHeaders: ['Content-Type', 'Authorization', 'Cookie'],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// Настройка сессии
|
// Настройка сессии
|
||||||
app.use(session({
|
app.use(
|
||||||
store: new pgSession({
|
session({
|
||||||
pool,
|
store: new pgSession({
|
||||||
tableName: 'session',
|
pool,
|
||||||
}),
|
tableName: 'session',
|
||||||
secret: process.env.SESSION_SECRET || 'hb3atoken',
|
}),
|
||||||
name: 'sessionId',
|
secret: process.env.SESSION_SECRET || 'hb3atoken',
|
||||||
resave: false,
|
name: 'sessionId',
|
||||||
saveUninitialized: true,
|
resave: false,
|
||||||
cookie: {
|
saveUninitialized: true,
|
||||||
maxAge: 30 * 24 * 60 * 60 * 1000,
|
cookie: {
|
||||||
httpOnly: true,
|
maxAge: 30 * 24 * 60 * 60 * 1000,
|
||||||
secure: false,
|
httpOnly: true,
|
||||||
sameSite: 'lax',
|
secure: false,
|
||||||
path: '/'
|
sameSite: 'lax',
|
||||||
}
|
path: '/',
|
||||||
}));
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// Добавим middleware для проверки сессии
|
// Добавим middleware для проверки сессии
|
||||||
app.use(async (req, res, next) => {
|
app.use(async (req, res, next) => {
|
||||||
console.log('Request cookies:', req.headers.cookie);
|
console.log('Request cookies:', req.headers.cookie);
|
||||||
console.log('Session ID:', req.sessionID);
|
console.log('Session ID:', req.sessionID);
|
||||||
|
|
||||||
// Проверяем сессию в базе данных
|
// Проверяем сессию в базе данных
|
||||||
if (req.sessionID) {
|
if (req.sessionID) {
|
||||||
const result = await pool.query(
|
const result = await pool.query('SELECT sess FROM session WHERE sid = $1', [req.sessionID]);
|
||||||
'SELECT sess FROM session WHERE sid = $1',
|
|
||||||
[req.sessionID]
|
|
||||||
);
|
|
||||||
console.log('Session from DB:', result.rows[0]?.sess);
|
console.log('Session from DB:', result.rows[0]?.sess);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,28 +81,31 @@ app.use(async (req, res, next) => {
|
|||||||
const token = authHeader.split(' ')[1];
|
const token = authHeader.split(' ')[1];
|
||||||
try {
|
try {
|
||||||
// Находим пользователя по токену
|
// Находим пользователя по токену
|
||||||
const { rows } = await pool.query(`
|
const { rows } = await pool.query(
|
||||||
|
`
|
||||||
SELECT u.id,
|
SELECT u.id,
|
||||||
(u.role = 'admin') as is_admin,
|
(u.role = 'admin') as is_admin,
|
||||||
u.address
|
u.address
|
||||||
FROM users u
|
FROM users u
|
||||||
WHERE u.id = $1
|
WHERE u.id = $1
|
||||||
`, [token]);
|
`,
|
||||||
|
[token]
|
||||||
|
);
|
||||||
|
|
||||||
if (rows.length > 0) {
|
if (rows.length > 0) {
|
||||||
const user = rows[0];
|
const user = rows[0];
|
||||||
req.session.userId = user.id;
|
req.session.userId = user.id;
|
||||||
req.session.address = user.address;
|
req.session.address = user.address;
|
||||||
req.session.isAdmin = user.is_admin;
|
req.session.isAdmin = user.is_admin;
|
||||||
req.session.authenticated = true;
|
req.session.authenticated = true;
|
||||||
|
|
||||||
await new Promise(resolve => req.session.save(resolve));
|
await new Promise((resolve) => req.session.save(resolve));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error checking auth header:', error);
|
console.error('Error checking auth header:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -110,9 +114,11 @@ app.use(express.json());
|
|||||||
app.use(express.urlencoded({ extended: true }));
|
app.use(express.urlencoded({ extended: true }));
|
||||||
|
|
||||||
// Настройка безопасности
|
// Настройка безопасности
|
||||||
app.use(helmet({
|
app.use(
|
||||||
contentSecurityPolicy: false // Отключаем CSP для разработки
|
helmet({
|
||||||
}));
|
contentSecurityPolicy: false, // Отключаем CSP для разработки
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// Логирование запросов
|
// Логирование запросов
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
@@ -156,32 +162,35 @@ app.get('/api/health', async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
// Проверяем подключение к БД
|
// Проверяем подключение к БД
|
||||||
await pool.query('SELECT NOW()');
|
await pool.query('SELECT NOW()');
|
||||||
|
|
||||||
// Проверяем AI сервис
|
// Проверяем AI сервис
|
||||||
const aiStatus = await aiAssistant.checkHealth();
|
const aiStatus = await aiAssistant.checkHealth();
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
status: 'ok',
|
status: 'ok',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
database: 'connected',
|
database: 'connected',
|
||||||
ai: aiStatus
|
ai: aiStatus,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Health check failed:', error);
|
logger.error('Health check failed:', error);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
status: 'error',
|
status: 'error',
|
||||||
error: error.message
|
error: error.message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Очистка старых сессий
|
// Очистка старых сессий
|
||||||
setInterval(async () => {
|
setInterval(
|
||||||
try {
|
async () => {
|
||||||
await pool.query('DELETE FROM session WHERE expire < NOW()');
|
try {
|
||||||
} catch (error) {
|
await pool.query('DELETE FROM session WHERE expire < NOW()');
|
||||||
console.error('Error cleaning old sessions:', error);
|
} catch (error) {
|
||||||
}
|
console.error('Error cleaning old sessions:', error);
|
||||||
}, 15 * 60 * 1000); // Каждые 15 минут
|
}
|
||||||
|
},
|
||||||
|
15 * 60 * 1000
|
||||||
|
); // Каждые 15 минут
|
||||||
|
|
||||||
module.exports = { app, nonceStore };
|
module.exports = { app, nonceStore };
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ const sessionConfig = {
|
|||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: process.env.NODE_ENV === 'production',
|
secure: process.env.NODE_ENV === 'production',
|
||||||
sameSite: 'lax',
|
sameSite: 'lax',
|
||||||
path: '/'
|
path: '/',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
sessionMiddleware: session(sessionConfig)
|
sessionMiddleware: session(sessionConfig),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ const pool = new Pool({
|
|||||||
pool.query('SELECT NOW()', (err, res) => {
|
pool.query('SELECT NOW()', (err, res) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('Ошибка подключения к базе данных:', err);
|
console.error('Ошибка подключения к базе данных:', err);
|
||||||
|
|
||||||
// Пробуем альтернативное подключение
|
// Пробуем альтернативное подключение
|
||||||
console.log('Попытка альтернативного подключения через прямые параметры...');
|
console.log('Попытка альтернативного подключения через прямые параметры...');
|
||||||
|
|
||||||
const altPool = new Pool({
|
const altPool = new Pool({
|
||||||
host: process.env.DB_HOST || 'localhost',
|
host: process.env.DB_HOST || 'localhost',
|
||||||
port: parseInt(process.env.DB_PORT || '5432'),
|
port: parseInt(process.env.DB_PORT || '5432'),
|
||||||
@@ -30,7 +30,7 @@ pool.query('SELECT NOW()', (err, res) => {
|
|||||||
user: process.env.DB_USER || 'dapp_user',
|
user: process.env.DB_USER || 'dapp_user',
|
||||||
password: process.env.DB_PASSWORD,
|
password: process.env.DB_PASSWORD,
|
||||||
});
|
});
|
||||||
|
|
||||||
altPool.query('SELECT NOW()', (altErr, altRes) => {
|
altPool.query('SELECT NOW()', (altErr, altRes) => {
|
||||||
if (altErr) {
|
if (altErr) {
|
||||||
console.error('Альтернативное подключение тоже не удалось:', altErr);
|
console.error('Альтернативное подключение тоже не удалось:', altErr);
|
||||||
@@ -56,10 +56,13 @@ const query = (text, params) => {
|
|||||||
// Функция для сохранения гостевого сообщения в базе данных
|
// Функция для сохранения гостевого сообщения в базе данных
|
||||||
async function saveGuestMessageToDatabase(message, language, guestId) {
|
async function saveGuestMessageToDatabase(message, language, guestId) {
|
||||||
try {
|
try {
|
||||||
await query(`
|
await query(
|
||||||
|
`
|
||||||
INSERT INTO guest_messages (guest_id, content, language, created_at)
|
INSERT INTO guest_messages (guest_id, content, language, created_at)
|
||||||
VALUES ($1, $2, $3, NOW())
|
VALUES ($1, $2, $3, NOW())
|
||||||
`, [guestId, message, language]);
|
`,
|
||||||
|
[guestId, message, language]
|
||||||
|
);
|
||||||
console.log('Гостевое сообщение успешно сохранено:', message);
|
console.log('Гостевое сообщение успешно сохранено:', message);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка при сохранении гостевого сообщения:', error);
|
console.error('Ошибка при сохранении гостевого сообщения:', error);
|
||||||
|
|||||||
@@ -18,4 +18,4 @@ pool.query('SELECT NOW()', (err, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { pool };
|
module.exports = { pool };
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ async function initRoles() {
|
|||||||
WHERE table_name = 'roles'
|
WHERE table_name = 'roles'
|
||||||
);
|
);
|
||||||
`);
|
`);
|
||||||
|
|
||||||
if (!tableExists.rows[0].exists) {
|
if (!tableExists.rows[0].exists) {
|
||||||
// Создаем таблицу roles
|
// Создаем таблицу roles
|
||||||
await pool.query(`
|
await pool.query(`
|
||||||
@@ -24,40 +24,44 @@ async function initRoles() {
|
|||||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
);
|
);
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// Добавляем роли
|
// Добавляем роли
|
||||||
await pool.query(`
|
await pool.query(`
|
||||||
INSERT INTO roles (id, name, description) VALUES
|
INSERT INTO roles (id, name, description) VALUES
|
||||||
(3, 'user', 'Обычный пользователь'),
|
(3, 'user', 'Обычный пользователь'),
|
||||||
(4, 'admin', 'Администратор с полным доступом');
|
(4, 'admin', 'Администратор с полным доступом');
|
||||||
`);
|
`);
|
||||||
|
|
||||||
console.log('Таблица roles создана и заполнена');
|
console.log('Таблица roles создана и заполнена');
|
||||||
} else {
|
} else {
|
||||||
// Проверяем наличие ролей
|
// Проверяем наличие ролей
|
||||||
const rolesExist = await pool.query(`
|
const rolesExist = await pool.query(`
|
||||||
SELECT COUNT(*) FROM roles WHERE id IN (3, 4);
|
SELECT COUNT(*) FROM roles WHERE id IN (3, 4);
|
||||||
`);
|
`);
|
||||||
|
|
||||||
if (rolesExist.rows[0].count < 2) {
|
if (rolesExist.rows[0].count < 2) {
|
||||||
// Добавляем недостающие роли
|
// Добавляем недостающие роли
|
||||||
const userRoleExists = await pool.query(`SELECT EXISTS (SELECT FROM roles WHERE name = 'user');`);
|
const userRoleExists = await pool.query(
|
||||||
const adminRoleExists = await pool.query(`SELECT EXISTS (SELECT FROM roles WHERE name = 'admin');`);
|
`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) {
|
if (!userRoleExists.rows[0].exists) {
|
||||||
await pool.query(`
|
await pool.query(`
|
||||||
INSERT INTO roles (id, name, description) VALUES
|
INSERT INTO roles (id, name, description) VALUES
|
||||||
(3, 'user', 'Обычный пользователь');
|
(3, 'user', 'Обычный пользователь');
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!adminRoleExists.rows[0].exists) {
|
if (!adminRoleExists.rows[0].exists) {
|
||||||
await pool.query(`
|
await pool.query(`
|
||||||
INSERT INTO roles (id, name, description) VALUES
|
INSERT INTO roles (id, name, description) VALUES
|
||||||
(4, 'admin', 'Администратор с полным доступом');
|
(4, 'admin', 'Администратор с полным доступом');
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Таблица roles обновлена');
|
console.log('Таблица roles обновлена');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,31 +84,29 @@ async function initializeDatabase() {
|
|||||||
|
|
||||||
// Путь к папке с миграциями
|
// Путь к папке с миграциями
|
||||||
const migrationsPath = path.join(__dirname, 'migrations');
|
const migrationsPath = path.join(__dirname, 'migrations');
|
||||||
|
|
||||||
// Получаем все файлы миграций
|
// Получаем все файлы миграций
|
||||||
const migrationFiles = fs.readdirSync(migrationsPath)
|
const migrationFiles = fs
|
||||||
.filter(file => file.endsWith('.sql'))
|
.readdirSync(migrationsPath)
|
||||||
|
.filter((file) => file.endsWith('.sql'))
|
||||||
.sort();
|
.sort();
|
||||||
|
|
||||||
// Получаем выполненные миграции
|
// Получаем выполненные миграции
|
||||||
const { rows } = await pool.query('SELECT name FROM migrations');
|
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) {
|
for (const file of migrationFiles) {
|
||||||
if (!executedMigrations.has(file)) {
|
if (!executedMigrations.has(file)) {
|
||||||
const filePath = path.join(migrationsPath, file);
|
const filePath = path.join(migrationsPath, file);
|
||||||
const sql = fs.readFileSync(filePath, 'utf8');
|
const sql = fs.readFileSync(filePath, 'utf8');
|
||||||
|
|
||||||
logger.info(`Executing migration: ${file}`);
|
logger.info(`Executing migration: ${file}`);
|
||||||
await pool.query(sql);
|
await pool.query(sql);
|
||||||
|
|
||||||
// Записываем выполненную миграцию
|
// Записываем выполненную миграцию
|
||||||
await pool.query(
|
await pool.query('INSERT INTO migrations (name) VALUES ($1)', [file]);
|
||||||
'INSERT INTO migrations (name) VALUES ($1)',
|
|
||||||
[file]
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.info(`Migration completed: ${file}`);
|
logger.info(`Migration completed: ${file}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,4 +118,4 @@ async function initializeDatabase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { initializeDatabase };
|
module.exports = { initializeDatabase };
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
Система идентификации пользователей построена на следующих таблицах:
|
Система идентификации пользователей построена на следующих таблицах:
|
||||||
|
|
||||||
1. **users** - Основная таблица пользователей
|
1. **users** - Основная таблица пользователей
|
||||||
|
|
||||||
- `id SERIAL PRIMARY KEY` - Основной идентификатор пользователя
|
- `id SERIAL PRIMARY KEY` - Основной идентификатор пользователя
|
||||||
- `status` - Статус пользователя (active, blocked)
|
- `status` - Статус пользователя (active, blocked)
|
||||||
- `role` - Роль пользователя (user, admin)
|
- `role` - Роль пользователя (user, admin)
|
||||||
@@ -14,6 +15,7 @@
|
|||||||
- Поля `username`, `email` и `address` являются устаревшими и должны быть NULL
|
- Поля `username`, `email` и `address` являются устаревшими и должны быть NULL
|
||||||
|
|
||||||
2. **user_identities** - Таблица идентификаторов пользователей
|
2. **user_identities** - Таблица идентификаторов пользователей
|
||||||
|
|
||||||
- `id SERIAL PRIMARY KEY` - Идентификатор записи
|
- `id SERIAL PRIMARY KEY` - Идентификатор записи
|
||||||
- `user_id INTEGER REFERENCES users(id)` - Ссылка на пользователя
|
- `user_id INTEGER REFERENCES users(id)` - Ссылка на пользователя
|
||||||
- `provider VARCHAR(50)` - Тип идентификатора (email, wallet, telegram, username)
|
- `provider VARCHAR(50)` - Тип идентификатора (email, wallet, telegram, username)
|
||||||
@@ -22,6 +24,7 @@
|
|||||||
- Ограничение `CHECK (provider IN ('email', 'wallet', 'telegram', 'username'))` - запрещает тип 'guest'
|
- Ограничение `CHECK (provider IN ('email', 'wallet', 'telegram', 'username'))` - запрещает тип 'guest'
|
||||||
|
|
||||||
3. **guest_user_mapping** - Таблица связи гостевых идентификаторов с пользователями
|
3. **guest_user_mapping** - Таблица связи гостевых идентификаторов с пользователями
|
||||||
|
|
||||||
- `id SERIAL PRIMARY KEY` - Идентификатор записи
|
- `id SERIAL PRIMARY KEY` - Идентификатор записи
|
||||||
- `user_id INTEGER REFERENCES users(id)` - Ссылка на пользователя
|
- `user_id INTEGER REFERENCES users(id)` - Ссылка на пользователя
|
||||||
- `guest_id VARCHAR(255)` - Гостевой идентификатор
|
- `guest_id VARCHAR(255)` - Гостевой идентификатор
|
||||||
@@ -29,6 +32,7 @@
|
|||||||
- Уникальный ключ `guest_id`
|
- Уникальный ключ `guest_id`
|
||||||
|
|
||||||
4. **messages** - Таблица сообщений
|
4. **messages** - Таблица сообщений
|
||||||
|
|
||||||
- `id SERIAL PRIMARY KEY` - Идентификатор сообщения
|
- `id SERIAL PRIMARY KEY` - Идентификатор сообщения
|
||||||
- `conversation_id INTEGER REFERENCES conversations(id)` - Ссылка на диалог
|
- `conversation_id INTEGER REFERENCES conversations(id)` - Ссылка на диалог
|
||||||
- `user_id INTEGER REFERENCES users(id)` - Прямая ссылка на пользователя
|
- `user_id INTEGER REFERENCES users(id)` - Прямая ссылка на пользователя
|
||||||
@@ -56,7 +60,6 @@
|
|||||||
- Создается запись в таблице `users`
|
- Создается запись в таблице `users`
|
||||||
- Создается запись в таблице `user_identities` с соответствующим провайдером
|
- Создается запись в таблице `user_identities` с соответствующим провайдером
|
||||||
- Гостевой ID сохраняется в таблице `guest_user_mapping` (не в user_identities)
|
- Гостевой ID сохраняется в таблице `guest_user_mapping` (не в user_identities)
|
||||||
|
|
||||||
2. После аутентификации система автоматически обрабатывает гостевые сообщения:
|
2. После аутентификации система автоматически обрабатывает гостевые сообщения:
|
||||||
- Вызывается метод `linkGuestMessages`
|
- Вызывается метод `linkGuestMessages`
|
||||||
- Создается новый диалог для гостевых сообщений
|
- Создается новый диалог для гостевых сообщений
|
||||||
@@ -69,7 +72,7 @@
|
|||||||
Если пользователь аутентифицируется разными способами, система может объединить его данные:
|
Если пользователь аутентифицируется разными способами, система может объединить его данные:
|
||||||
|
|
||||||
1. Система проверяет связанных пользователей через `user_identities`
|
1. Система проверяет связанных пользователей через `user_identities`
|
||||||
2. Если находятся связанные пользователи, вызывается метод `migrateUserData`
|
2. Если находятся связанные пользователи, вызывается метод `migrateUserData`
|
||||||
3. Данные от вторичных аккаунтов мигрируют к основному:
|
3. Данные от вторичных аккаунтов мигрируют к основному:
|
||||||
- Идентификаторы в таблице `user_identities`
|
- Идентификаторы в таблице `user_identities`
|
||||||
- Гостевые связи в таблице `guest_user_mapping`
|
- Гостевые связи в таблице `guest_user_mapping`
|
||||||
@@ -89,6 +92,7 @@
|
|||||||
## Обработка ошибок
|
## Обработка ошибок
|
||||||
|
|
||||||
1. Если возникает ошибка при обработке гостевых сообщений, система:
|
1. Если возникает ошибка при обработке гостевых сообщений, система:
|
||||||
|
|
||||||
- Логирует ошибку
|
- Логирует ошибку
|
||||||
- Продолжает попытки обработки при следующих авторизациях
|
- Продолжает попытки обработки при следующих авторизациях
|
||||||
- Не удаляет гостевые сообщения до успешной обработки
|
- Не удаляет гостевые сообщения до успешной обработки
|
||||||
@@ -98,6 +102,7 @@
|
|||||||
## Оптимизации
|
## Оптимизации
|
||||||
|
|
||||||
1. Индексы созданы для всех полей, используемых в запросах:
|
1. Индексы созданы для всех полей, используемых в запросах:
|
||||||
|
|
||||||
- `user_identities(user_id)`
|
- `user_identities(user_id)`
|
||||||
- `user_identities(provider, provider_id)`
|
- `user_identities(provider, provider_id)`
|
||||||
- `guest_user_mapping(guest_id)`
|
- `guest_user_mapping(guest_id)`
|
||||||
@@ -106,6 +111,7 @@
|
|||||||
- `messages(conversation_id)`
|
- `messages(conversation_id)`
|
||||||
|
|
||||||
2. Триггеры автоматически поддерживают целостность данных:
|
2. Триггеры автоматически поддерживают целостность данных:
|
||||||
|
|
||||||
- Автоматическое заполнение `user_id` в таблице `messages`
|
- Автоматическое заполнение `user_id` в таблице `messages`
|
||||||
- Очистка неиспользуемых полей в таблице `users`
|
- Очистка неиспользуемых полей в таблице `users`
|
||||||
|
|
||||||
@@ -117,6 +123,7 @@
|
|||||||
## Функции для диагностики
|
## Функции для диагностики
|
||||||
|
|
||||||
1. **verify_migration_017()** - проверяет состояние гостевых идентификаторов
|
1. **verify_migration_017()** - проверяет состояние гостевых идентификаторов
|
||||||
|
|
||||||
- `guest_identities_count` - количество гостевых идентификаторов в таблице user_identities
|
- `guest_identities_count` - количество гостевых идентификаторов в таблице user_identities
|
||||||
- `guest_mapping_count` - количество записей в таблице guest_user_mapping
|
- `guest_mapping_count` - количество записей в таблице guest_user_mapping
|
||||||
- `missing_mappings` - количество гостевых ID, которые отсутствуют в guest_user_mapping
|
- `missing_mappings` - количество гостевых ID, которые отсутствуют в guest_user_mapping
|
||||||
@@ -127,4 +134,4 @@
|
|||||||
- `wallet_identities` - количество идентификаторов wallet
|
- `wallet_identities` - количество идентификаторов wallet
|
||||||
- `email_identities` - количество идентификаторов email
|
- `email_identities` - количество идентификаторов email
|
||||||
- `telegram_identities` - количество идентификаторов telegram
|
- `telegram_identities` - количество идентификаторов telegram
|
||||||
- `duplicate_provider_ids` - количество дублирующихся идентификаторов
|
- `duplicate_provider_ids` - количество дублирующихся идентификаторов
|
||||||
|
|||||||
@@ -47,10 +47,10 @@ CREATE TABLE IF NOT EXISTS example_table (
|
|||||||
);
|
);
|
||||||
|
|
||||||
-- Добавление колонки, если она отсутствует
|
-- Добавление колонки, если она отсутствует
|
||||||
DO $$
|
DO $$
|
||||||
BEGIN
|
BEGIN
|
||||||
IF NOT EXISTS (
|
IF NOT EXISTS (
|
||||||
SELECT 1 FROM information_schema.columns
|
SELECT 1 FROM information_schema.columns
|
||||||
WHERE table_name = 'example_table' AND column_name = 'new_column'
|
WHERE table_name = 'example_table' AND column_name = 'new_column'
|
||||||
) THEN
|
) THEN
|
||||||
ALTER TABLE example_table ADD COLUMN new_column INTEGER;
|
ALTER TABLE example_table ADD COLUMN new_column INTEGER;
|
||||||
@@ -69,6 +69,7 @@ END $$;
|
|||||||
- `verify_identity_system()` - проверка состояния системы идентификации пользователей
|
- `verify_identity_system()` - проверка состояния системы идентификации пользователей
|
||||||
|
|
||||||
Пример использования:
|
Пример использования:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT * FROM verify_identity_system();
|
SELECT * FROM verify_identity_system();
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import globals from 'globals';
|
const globals = require('globals');
|
||||||
|
|
||||||
export default [
|
module.exports = [
|
||||||
{
|
{
|
||||||
ignores: ['node_modules/**', 'artifacts/**', 'sessions/**', 'logs/**', 'data/**'],
|
ignores: ['node_modules/**', 'artifacts/**', 'sessions/**', 'logs/**', 'data/**'],
|
||||||
},
|
},
|
||||||
@@ -8,10 +8,11 @@ export default [
|
|||||||
files: ['**/*.js'],
|
files: ['**/*.js'],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
ecmaVersion: 2022,
|
ecmaVersion: 2022,
|
||||||
sourceType: 'module',
|
sourceType: 'module', // Оставляем module, т.к. ESLint может анализировать ES модули
|
||||||
globals: {
|
globals: {
|
||||||
...globals.node,
|
...globals.node,
|
||||||
...globals.es2021,
|
...globals.es2021,
|
||||||
|
// Для тестов Mocha
|
||||||
describe: 'readonly',
|
describe: 'readonly',
|
||||||
it: 'readonly',
|
it: 'readonly',
|
||||||
beforeEach: 'readonly',
|
beforeEach: 'readonly',
|
||||||
@@ -21,10 +22,10 @@ export default [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'no-unused-vars': 'off',
|
'no-unused-vars': 'warn', // Лучше warn, чем off
|
||||||
'no-console': 'off',
|
'no-console': 'off', // Оставляем off для логов в Node.js
|
||||||
'no-undef': 'error',
|
'no-undef': 'error',
|
||||||
'no-duplicate-imports': 'error',
|
'no-duplicate-imports': 'error',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -13,19 +13,19 @@ const requireAuth = async (req, res, next) => {
|
|||||||
console.log('Session in requireAuth:', {
|
console.log('Session in requireAuth:', {
|
||||||
id: req.sessionID,
|
id: req.sessionID,
|
||||||
userId: req.session?.userId,
|
userId: req.session?.userId,
|
||||||
authenticated: req.session?.authenticated
|
authenticated: req.session?.authenticated,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Проверяем сессию
|
// Проверяем сессию
|
||||||
if (req.session?.authenticated && req.session?.userId) {
|
if (req.session?.authenticated && req.session?.userId) {
|
||||||
// Обновляем время жизни сессии
|
// Обновляем время жизни сессии
|
||||||
req.session.touch();
|
req.session.touch();
|
||||||
|
|
||||||
req.user = {
|
req.user = {
|
||||||
userId: req.session.userId,
|
userId: req.session.userId,
|
||||||
address: req.session.address,
|
address: req.session.address,
|
||||||
isAdmin: req.session.isAdmin,
|
isAdmin: req.session.isAdmin,
|
||||||
authType: req.session.authType
|
authType: req.session.authType,
|
||||||
};
|
};
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
@@ -34,19 +34,22 @@ const requireAuth = async (req, res, next) => {
|
|||||||
const authHeader = req.headers.authorization;
|
const authHeader = req.headers.authorization;
|
||||||
if (authHeader?.startsWith('Bearer ')) {
|
if (authHeader?.startsWith('Bearer ')) {
|
||||||
const address = authHeader.split(' ')[1];
|
const address = authHeader.split(' ')[1];
|
||||||
|
|
||||||
if (address.startsWith('0x')) {
|
if (address.startsWith('0x')) {
|
||||||
const result = await db.query(`
|
const result = await db.query(
|
||||||
|
`
|
||||||
SELECT u.id, u.is_admin
|
SELECT u.id, u.is_admin
|
||||||
FROM users u
|
FROM users u
|
||||||
JOIN user_identities ui ON u.id = ui.user_id
|
JOIN user_identities ui ON u.id = ui.user_id
|
||||||
WHERE ui.identity_type = 'wallet'
|
WHERE ui.identity_type = 'wallet'
|
||||||
AND LOWER(ui.identity_value) = LOWER($1)
|
AND LOWER(ui.identity_value) = LOWER($1)
|
||||||
`, [address]);
|
`,
|
||||||
|
[address]
|
||||||
|
);
|
||||||
|
|
||||||
if (result.rows.length > 0) {
|
if (result.rows.length > 0) {
|
||||||
const user = result.rows[0];
|
const user = result.rows[0];
|
||||||
|
|
||||||
// Создаем новую сессию
|
// Создаем новую сессию
|
||||||
req.session.regenerate(async (err) => {
|
req.session.regenerate(async (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -68,7 +71,7 @@ const requireAuth = async (req, res, next) => {
|
|||||||
userId: user.id,
|
userId: user.id,
|
||||||
address: address,
|
address: address,
|
||||||
isAdmin: user.is_admin,
|
isAdmin: user.is_admin,
|
||||||
authType: 'wallet'
|
authType: 'wallet',
|
||||||
};
|
};
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
@@ -111,7 +114,9 @@ async function requireAdmin(req, res, next) {
|
|||||||
|
|
||||||
// Проверка через ID пользователя
|
// Проверка через ID пользователя
|
||||||
if (req.session.userId) {
|
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) {
|
if (userResult.rows.length > 0 && userResult.rows[0].role === USER_ROLES.ADMIN) {
|
||||||
// Обновляем сессию
|
// Обновляем сессию
|
||||||
req.session.isAdmin = true;
|
req.session.isAdmin = true;
|
||||||
@@ -146,7 +151,9 @@ function requireRole(role) {
|
|||||||
|
|
||||||
// Проверка через ID пользователя
|
// Проверка через ID пользователя
|
||||||
if (req.session.userId) {
|
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) {
|
if (userResult.rows.length > 0 && userResult.rows[0].role === role) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
@@ -192,5 +199,5 @@ module.exports = {
|
|||||||
requireAuth,
|
requireAuth,
|
||||||
requireAdmin,
|
requireAdmin,
|
||||||
requireRole,
|
requireRole,
|
||||||
checkRole
|
checkRole,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ function errorHandler(err, req, res, next) {
|
|||||||
url: req.originalUrl,
|
url: req.originalUrl,
|
||||||
method: req.method,
|
method: req.method,
|
||||||
ip: req.ip,
|
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'
|
const devError = process.env.NODE_ENV === 'development' ? { stack: err.stack } : {};
|
||||||
? { stack: err.stack }
|
|
||||||
: {};
|
|
||||||
|
|
||||||
// Отправляем ответ клиенту
|
// Отправляем ответ клиенту
|
||||||
res.status(statusCode).json({
|
res.status(statusCode).json({
|
||||||
error: {
|
error: {
|
||||||
code: errorCode,
|
code: errorCode,
|
||||||
message: errorMessage,
|
message: errorMessage,
|
||||||
...devError
|
...devError,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,5 +65,5 @@ function createError(message, status) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
errorHandler,
|
errorHandler,
|
||||||
createError
|
createError,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,4 +11,4 @@ const requestLogger = (req, res, next) => {
|
|||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = requestLogger;
|
module.exports = requestLogger;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
const db = require('../db');
|
||||||
const { requireAdmin } = require('../middleware/auth');
|
const { requireAdmin } = require('../middleware/auth');
|
||||||
const authService = require('../services/auth-service');
|
const authService = require('../services/auth-service');
|
||||||
const logger = require('../utils/logger');
|
const logger = require('../utils/logger');
|
||||||
|
|||||||
@@ -35,29 +35,28 @@ router.get('/nonce', async (req, res) => {
|
|||||||
|
|
||||||
// Генерируем случайный nonce
|
// Генерируем случайный nonce
|
||||||
const nonce = crypto.randomBytes(16).toString('hex');
|
const nonce = crypto.randomBytes(16).toString('hex');
|
||||||
|
|
||||||
// Проверяем, существует ли уже nonce для этого адреса
|
// Проверяем, существует ли уже nonce для этого адреса
|
||||||
const existingNonce = await db.query(
|
const existingNonce = await db.query('SELECT id FROM nonces WHERE identity_value = $1', [
|
||||||
'SELECT id FROM nonces WHERE identity_value = $1',
|
address.toLowerCase(),
|
||||||
[address.toLowerCase()]
|
]);
|
||||||
);
|
|
||||||
|
|
||||||
if (existingNonce.rows.length > 0) {
|
if (existingNonce.rows.length > 0) {
|
||||||
// Обновляем существующий nonce
|
// Обновляем существующий nonce
|
||||||
await db.query(
|
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()]
|
[nonce, address.toLowerCase()]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Создаем новый nonce
|
// Создаем новый nonce
|
||||||
await db.query(
|
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]
|
[address.toLowerCase(), nonce]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Nonce ${nonce} сохранен для адреса ${address}`);
|
logger.info(`Nonce ${nonce} сохранен для адреса ${address}`);
|
||||||
|
|
||||||
res.json({ nonce });
|
res.json({ nonce });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error generating nonce:', error);
|
logger.error('Error generating nonce:', error);
|
||||||
@@ -69,53 +68,58 @@ router.get('/nonce', async (req, res) => {
|
|||||||
router.post('/verify', async (req, res) => {
|
router.post('/verify', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { address, message, signature } = req.body;
|
const { address, message, signature } = req.body;
|
||||||
|
|
||||||
logger.info(`[verify] Verifying signature for address: ${address}`);
|
logger.info(`[verify] Verifying signature for address: ${address}`);
|
||||||
|
|
||||||
// Сохраняем гостевые ID до проверки
|
// Сохраняем гостевые ID до проверки
|
||||||
const guestId = req.session.guestId;
|
const guestId = req.session.guestId;
|
||||||
const previousGuestId = req.session.previousGuestId;
|
const previousGuestId = req.session.previousGuestId;
|
||||||
|
|
||||||
// Проверяем подпись
|
// Проверяем подпись
|
||||||
const isValid = await authService.verifySignature(message, signature, address);
|
const isValid = await authService.verifySignature(message, signature, address);
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
return res.status(401).json({ success: false, error: 'Invalid signature' });
|
return res.status(401).json({ success: false, error: 'Invalid signature' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Нормализуем адрес для использования в запросах
|
// Нормализуем адрес для использования в запросах
|
||||||
const normalizedAddress = ethers.getAddress(address).toLowerCase();
|
const normalizedAddress = ethers.getAddress(address).toLowerCase();
|
||||||
|
|
||||||
// Проверяем nonce
|
// Проверяем nonce
|
||||||
const nonceResult = await db.query('SELECT nonce FROM nonces WHERE identity_value = $1', [normalizedAddress]);
|
const nonceResult = await db.query('SELECT nonce FROM nonces WHERE identity_value = $1', [
|
||||||
if (nonceResult.rows.length === 0 || nonceResult.rows[0].nonce !== message.match(/Nonce: ([^\n]+)/)[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' });
|
return res.status(401).json({ success: false, error: 'Invalid nonce' });
|
||||||
}
|
}
|
||||||
|
|
||||||
let userId;
|
let userId;
|
||||||
let isAdmin = false;
|
let isAdmin = false;
|
||||||
|
|
||||||
// Проверяем, авторизован ли пользователь уже
|
// Проверяем, авторизован ли пользователь уже
|
||||||
if (req.session.authenticated && req.session.userId) {
|
if (req.session.authenticated && req.session.userId) {
|
||||||
// Если пользователь уже авторизован, привязываем кошелек к существующему пользователю
|
// Если пользователь уже авторизован, привязываем кошелек к существующему пользователю
|
||||||
userId = 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
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Связываем кошелек с пользователем через identity-service для предотвращения дубликатов
|
||||||
|
const linkResult = await authService.linkIdentity(userId, 'wallet', address);
|
||||||
|
|
||||||
if (!linkResult.success && linkResult.error) {
|
if (!linkResult.success && linkResult.error) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: linkResult.error
|
error: linkResult.error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если linkResult.message содержит 'already exists', значит кошелек уже привязан
|
// Если 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 {
|
} else {
|
||||||
// Находим или создаем пользователя, если не авторизован
|
// Находим или создаем пользователя, если не авторизован
|
||||||
const result = await authService.findOrCreateUser(address);
|
const result = await authService.findOrCreateUser(address);
|
||||||
@@ -123,49 +127,48 @@ router.post('/verify', async (req, res) => {
|
|||||||
isAdmin = result.isAdmin;
|
isAdmin = result.isAdmin;
|
||||||
logger.info(`[verify] Found or created user ${userId} for wallet ${normalizedAddress}`);
|
logger.info(`[verify] Found or created user ${userId} for wallet ${normalizedAddress}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем идентификаторы гостевой сессии
|
// Сохраняем идентификаторы гостевой сессии
|
||||||
if (guestId) {
|
if (guestId) {
|
||||||
await identityService.saveIdentity(userId, 'guest', guestId, true);
|
await identityService.saveIdentity(userId, 'guest', guestId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (previousGuestId && previousGuestId !== guestId) {
|
if (previousGuestId && previousGuestId !== guestId) {
|
||||||
await identityService.saveIdentity(userId, 'guest', previousGuestId, true);
|
await identityService.saveIdentity(userId, 'guest', previousGuestId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем наличие админских токенов
|
// Проверяем наличие админских токенов
|
||||||
const adminStatus = await authService.checkAdminTokens(normalizedAddress);
|
const adminStatus = await authService.checkAdminTokens(normalizedAddress);
|
||||||
|
|
||||||
if (adminStatus) {
|
if (adminStatus) {
|
||||||
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]);
|
||||||
isAdmin = true;
|
isAdmin = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем сессию
|
// Обновляем сессию
|
||||||
req.session.userId = userId;
|
req.session.userId = userId;
|
||||||
req.session.authenticated = true;
|
req.session.authenticated = true;
|
||||||
req.session.authType = 'wallet';
|
req.session.authType = 'wallet';
|
||||||
req.session.isAdmin = adminStatus || isAdmin;
|
req.session.isAdmin = adminStatus || isAdmin;
|
||||||
req.session.address = normalizedAddress; // Всегда сохраняем нормализованный адрес
|
req.session.address = normalizedAddress; // Всегда сохраняем нормализованный адрес
|
||||||
|
|
||||||
// Удаляем временный ID
|
// Удаляем временный ID
|
||||||
delete req.session.tempUserId;
|
delete req.session.tempUserId;
|
||||||
|
|
||||||
// Сохраняем сессию
|
// Сохраняем сессию
|
||||||
await sessionService.saveSession(req.session);
|
await sessionService.saveSession(req.session);
|
||||||
|
|
||||||
// Связываем гостевые сообщения с пользователем
|
// Связываем гостевые сообщения с пользователем
|
||||||
await sessionService.linkGuestMessages(req.session, userId);
|
await sessionService.linkGuestMessages(req.session, userId);
|
||||||
|
|
||||||
// Возвращаем успешный ответ
|
// Возвращаем успешный ответ
|
||||||
return res.json({
|
return res.json({
|
||||||
success: true,
|
success: true,
|
||||||
userId,
|
userId,
|
||||||
address: normalizedAddress, // Возвращаем нормализованный адрес
|
address: normalizedAddress, // Возвращаем нормализованный адрес
|
||||||
isAdmin: adminStatus || isAdmin,
|
isAdmin: adminStatus || isAdmin,
|
||||||
authenticated: true
|
authenticated: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[verify] Error:', error);
|
logger.error('[verify] Error:', error);
|
||||||
res.status(500).json({ success: false, error: 'Server error' });
|
res.status(500).json({ success: false, error: 'Server error' });
|
||||||
@@ -176,17 +179,17 @@ router.post('/verify', async (req, res) => {
|
|||||||
router.post('/telegram/verify', async (req, res) => {
|
router.post('/telegram/verify', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { telegramId, verificationCode } = req.body;
|
const { telegramId, verificationCode } = req.body;
|
||||||
|
|
||||||
if (!telegramId || !verificationCode) {
|
if (!telegramId || !verificationCode) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Missing required fields'
|
error: 'Missing required fields',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем гостевой ID из текущей сессии
|
// Сохраняем гостевой ID из текущей сессии
|
||||||
const guestId = req.session.guestId;
|
const guestId = req.session.guestId;
|
||||||
|
|
||||||
// Передаем сессию в метод верификации
|
// Передаем сессию в метод верификации
|
||||||
const verificationResult = await authService.verifyTelegramAuth(
|
const verificationResult = await authService.verifyTelegramAuth(
|
||||||
telegramId,
|
telegramId,
|
||||||
@@ -197,7 +200,7 @@ router.post('/telegram/verify', async (req, res) => {
|
|||||||
if (!verificationResult.success) {
|
if (!verificationResult.success) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: verificationResult.error || 'Verification failed'
|
error: verificationResult.error || 'Verification failed',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,9 +208,9 @@ router.post('/telegram/verify', async (req, res) => {
|
|||||||
req.session.regenerate(async (err) => {
|
req.session.regenerate(async (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
logger.error('[telegram/verify] Error regenerating session:', err);
|
logger.error('[telegram/verify] Error regenerating session:', err);
|
||||||
return res.status(500).json({
|
return res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Session error'
|
error: 'Session error',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,7 +220,7 @@ router.post('/telegram/verify', async (req, res) => {
|
|||||||
req.session.authType = 'telegram';
|
req.session.authType = 'telegram';
|
||||||
req.session.authenticated = true;
|
req.session.authenticated = true;
|
||||||
req.session.role = verificationResult.role;
|
req.session.role = verificationResult.role;
|
||||||
|
|
||||||
// Восстанавливаем гостевой ID, если он был
|
// Восстанавливаем гостевой ID, если он был
|
||||||
if (guestId) {
|
if (guestId) {
|
||||||
req.session.guestId = guestId;
|
req.session.guestId = guestId;
|
||||||
@@ -236,14 +239,14 @@ router.post('/telegram/verify', async (req, res) => {
|
|||||||
userId: verificationResult.userId,
|
userId: verificationResult.userId,
|
||||||
role: verificationResult.role,
|
role: verificationResult.role,
|
||||||
telegramId,
|
telegramId,
|
||||||
isNewUser: verificationResult.isNewUser
|
isNewUser: verificationResult.isNewUser,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[telegram/verify] Error:', error);
|
logger.error('[telegram/verify] Error:', error);
|
||||||
return res.status(500).json({
|
return res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Internal server error'
|
error: 'Internal server error',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -252,26 +255,26 @@ router.post('/telegram/verify', async (req, res) => {
|
|||||||
router.post('/email/request', authLimiter, async (req, res) => {
|
router.post('/email/request', authLimiter, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { email } = req.body;
|
const { email } = req.body;
|
||||||
|
|
||||||
if (!email || !email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
|
if (!email || !email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
|
||||||
return res.status(400).json({ error: 'Invalid email format' });
|
return res.status(400).json({ error: 'Invalid email format' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Инициализация email аутентификации
|
// Инициализация email аутентификации
|
||||||
const result = await emailAuth.initEmailAuth(req.session, email);
|
const result = await emailAuth.initEmailAuth(req.session, email);
|
||||||
|
|
||||||
// Сохраняем сессию после установки pendingEmail
|
// Сохраняем сессию после установки pendingEmail
|
||||||
await sessionService.saveSession(req.session);
|
await sessionService.saveSession(req.session);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Код подтверждения отправлен на email'
|
message: 'Код подтверждения отправлен на email',
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: result.error || 'Ошибка отправки кода'
|
error: result.error || 'Ошибка отправки кода',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -284,150 +287,155 @@ router.post('/email/request', authLimiter, async (req, res) => {
|
|||||||
router.post('/email/verify-code', async (req, res) => {
|
router.post('/email/verify-code', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { code, email } = req.body;
|
const { code, email } = req.body;
|
||||||
|
|
||||||
if (!code) {
|
if (!code) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Код подтверждения обязателен'
|
error: 'Код подтверждения обязателен',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если email передан в запросе, сохраняем его в сессии
|
// Если email передан в запросе, сохраняем его в сессии
|
||||||
if (email && !req.session.pendingEmail) {
|
if (email && !req.session.pendingEmail) {
|
||||||
req.session.pendingEmail = email.toLowerCase();
|
req.session.pendingEmail = email.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!req.session.pendingEmail) {
|
if (!req.session.pendingEmail) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Email не найден в сессии. Пожалуйста, запросите код подтверждения снова.'
|
error: 'Email не найден в сессии. Пожалуйста, запросите код подтверждения снова.',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем гостевой ID до проверки
|
// Сохраняем гостевой ID до проверки
|
||||||
const guestId = req.session.guestId;
|
const guestId = req.session.guestId;
|
||||||
const previousGuestId = req.session.previousGuestId;
|
const previousGuestId = req.session.previousGuestId;
|
||||||
|
|
||||||
// Проверяем код через сервис верификации
|
// Проверяем код через сервис верификации
|
||||||
const verificationResult = await verificationService.verifyCode(
|
const verificationResult = await verificationService.verifyCode(
|
||||||
code,
|
code,
|
||||||
'email',
|
'email',
|
||||||
req.session.pendingEmail
|
req.session.pendingEmail
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!verificationResult.success) {
|
if (!verificationResult.success) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: verificationResult.error || 'Неверный код подтверждения'
|
error: verificationResult.error || 'Неверный код подтверждения',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем или создаем пользователя
|
// Получаем или создаем пользователя
|
||||||
let userId;
|
let userId;
|
||||||
let isNewAuth = false;
|
let isNewAuth = false;
|
||||||
|
|
||||||
// Проверяем, авторизован ли пользователь
|
// Проверяем, авторизован ли пользователь
|
||||||
if (req.session.authenticated && req.session.userId) {
|
if (req.session.authenticated && req.session.userId) {
|
||||||
// Связываем email с существующим пользователем
|
// Связываем email с существующим пользователем
|
||||||
userId = req.session.userId;
|
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
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Связываем email с текущим аккаунтом
|
||||||
|
const linkResult = await authService.linkIdentity(userId, 'email', req.session.pendingEmail);
|
||||||
|
|
||||||
// Сохраняем email в сессии
|
// Сохраняем email в сессии
|
||||||
req.session.email = req.session.pendingEmail;
|
req.session.email = req.session.pendingEmail;
|
||||||
|
|
||||||
// Удаляем временные данные
|
// Удаляем временные данные
|
||||||
delete req.session.pendingEmail;
|
delete req.session.pendingEmail;
|
||||||
|
|
||||||
// Сохраняем сессию
|
// Сохраняем сессию
|
||||||
await sessionService.saveSession(req.session);
|
await sessionService.saveSession(req.session);
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
success: true,
|
success: true,
|
||||||
userId,
|
userId,
|
||||||
email: req.session.email,
|
email: req.session.email,
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
linked: true
|
linked: true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Если пользователь не авторизован, ищем существующего пользователя или создаем нового
|
// Если пользователь не авторизован, ищем существующего пользователя или создаем нового
|
||||||
|
|
||||||
// Ищем существующего пользователя по email
|
// Ищем существующего пользователя по email
|
||||||
const existingUser = await identityService.findUserByIdentity(
|
const existingUser = await identityService.findUserByIdentity(
|
||||||
'email',
|
'email',
|
||||||
req.session.pendingEmail
|
req.session.pendingEmail
|
||||||
);
|
);
|
||||||
|
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
// Используем существующего пользователя
|
// Используем существующего пользователя
|
||||||
userId = existingUser.id;
|
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) {
|
} else if (req.session.userId) {
|
||||||
// Используем текущего пользователя
|
// Используем текущего пользователя
|
||||||
userId = 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) {
|
} else if (req.session.tempUserId) {
|
||||||
// Используем временного пользователя
|
// Используем временного пользователя
|
||||||
userId = 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 {
|
} else {
|
||||||
// Создаем нового пользователя
|
// Создаем нового пользователя
|
||||||
const newUser = await db.query(
|
const newUser = await db.query('INSERT INTO users (role) VALUES ($1) RETURNING id', [
|
||||||
'INSERT INTO users (role) VALUES ($1) RETURNING id',
|
'user',
|
||||||
['user']
|
]);
|
||||||
);
|
|
||||||
userId = newUser.rows[0].id;
|
userId = newUser.rows[0].id;
|
||||||
isNewAuth = true;
|
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 как идентификатор
|
// Сохраняем email как идентификатор
|
||||||
await identityService.saveIdentity(userId, 'email', req.session.pendingEmail, true);
|
await identityService.saveIdentity(userId, 'email', req.session.pendingEmail, true);
|
||||||
|
|
||||||
// Сохраняем гостевые идентификаторы
|
// Сохраняем гостевые идентификаторы
|
||||||
if (guestId) {
|
if (guestId) {
|
||||||
await identityService.saveIdentity(userId, 'guest', guestId, true);
|
await identityService.saveIdentity(userId, 'guest', guestId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (previousGuestId && previousGuestId !== guestId) {
|
if (previousGuestId && previousGuestId !== guestId) {
|
||||||
await identityService.saveIdentity(userId, 'guest', previousGuestId, true);
|
await identityService.saveIdentity(userId, 'guest', previousGuestId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Устанавливаем сессию
|
// Устанавливаем сессию
|
||||||
req.session.userId = userId;
|
req.session.userId = userId;
|
||||||
req.session.authenticated = true;
|
req.session.authenticated = true;
|
||||||
req.session.authType = 'email';
|
req.session.authType = 'email';
|
||||||
req.session.email = req.session.pendingEmail;
|
req.session.email = req.session.pendingEmail;
|
||||||
|
|
||||||
// Удаляем временные данные
|
// Удаляем временные данные
|
||||||
delete req.session.tempUserId;
|
delete req.session.tempUserId;
|
||||||
delete req.session.pendingEmail;
|
delete req.session.pendingEmail;
|
||||||
|
|
||||||
// Сохраняем сессию
|
// Сохраняем сессию
|
||||||
await sessionService.saveSession(req.session);
|
await sessionService.saveSession(req.session);
|
||||||
|
|
||||||
// Связываем гостевые сообщения
|
// Связываем гостевые сообщения
|
||||||
await sessionService.linkGuestMessages(req.session, userId);
|
await sessionService.linkGuestMessages(req.session, userId);
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
success: true,
|
success: true,
|
||||||
userId,
|
userId,
|
||||||
email: req.session.email,
|
email: req.session.email,
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
isNewAuth
|
isNewAuth,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[email/verify-code] Error:', error);
|
logger.error('[email/verify-code] Error:', error);
|
||||||
return res.status(500).json({
|
return res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Ошибка сервера'
|
error: 'Ошибка сервера',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -436,29 +444,29 @@ router.post('/email/verify-code', async (req, res) => {
|
|||||||
router.post('/telegram/init', async (req, res) => {
|
router.post('/telegram/init', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { verificationCode, botLink } = await initTelegramAuth(req.session);
|
const { verificationCode, botLink } = await initTelegramAuth(req.session);
|
||||||
|
|
||||||
if (!verificationCode || !botLink) {
|
if (!verificationCode || !botLink) {
|
||||||
throw new Error('Failed to generate verification code');
|
throw new Error('Failed to generate verification code');
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
verificationCode,
|
verificationCode,
|
||||||
botLink
|
botLink,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error initializing Telegram auth:', error);
|
logger.error('Error initializing Telegram auth:', error);
|
||||||
|
|
||||||
if (error.message === 'Telegram уже привязан к этому аккаунту') {
|
if (error.message === 'Telegram уже привязан к этому аккаунту') {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: error.message
|
error: error.message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Failed to initialize Telegram auth'
|
error: 'Failed to initialize Telegram auth',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -467,29 +475,29 @@ router.post('/telegram/init', async (req, res) => {
|
|||||||
router.post('/email/init', async (req, res) => {
|
router.post('/email/init', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { email } = req.body;
|
const { email } = req.body;
|
||||||
|
|
||||||
if (!email || !email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
|
if (!email || !email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Некорректный формат email'
|
error: 'Некорректный формат email',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Инициализация email аутентификации
|
// Инициализация email аутентификации
|
||||||
const result = await emailAuth.initEmailAuth(req.session, email);
|
const result = await emailAuth.initEmailAuth(req.session, email);
|
||||||
|
|
||||||
// Сохраняем сессию
|
// Сохраняем сессию
|
||||||
await sessionService.saveSession(req.session);
|
await sessionService.saveSession(req.session);
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Код верификации отправлен на email'
|
message: 'Код верификации отправлен на email',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error in email auth initialization:', error);
|
logger.error('Error in email auth initialization:', error);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Внутренняя ошибка сервера'
|
error: 'Внутренняя ошибка сервера',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -499,21 +507,20 @@ router.get('/check', async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const authenticated = req.session.authenticated || false;
|
const authenticated = req.session.authenticated || false;
|
||||||
const authType = req.session.authType || null;
|
const authType = req.session.authType || null;
|
||||||
|
|
||||||
let identities = [];
|
let identities = [];
|
||||||
let isAdmin = false;
|
let isAdmin = false;
|
||||||
|
|
||||||
if (authenticated && req.session.userId) {
|
if (authenticated && req.session.userId) {
|
||||||
// Если пользователь аутентифицирован, получаем его идентификаторы из БД
|
// Если пользователь аутентифицирован, получаем его идентификаторы из БД
|
||||||
try {
|
try {
|
||||||
identities = await identityService.getUserIdentities(req.session.userId);
|
identities = await identityService.getUserIdentities(req.session.userId);
|
||||||
|
|
||||||
// Проверяем роль пользователя
|
// Проверяем роль пользователя
|
||||||
const roleResult = await db.query(
|
const roleResult = await db.query('SELECT role FROM users WHERE id = $1', [
|
||||||
'SELECT role FROM users WHERE id = $1',
|
req.session.userId,
|
||||||
[req.session.userId]
|
]);
|
||||||
);
|
|
||||||
|
|
||||||
if (roleResult.rows.length > 0) {
|
if (roleResult.rows.length > 0) {
|
||||||
isAdmin = roleResult.rows[0].role === 'admin';
|
isAdmin = roleResult.rows[0].role === 'admin';
|
||||||
req.session.isAdmin = isAdmin;
|
req.session.isAdmin = isAdmin;
|
||||||
@@ -522,15 +529,15 @@ router.get('/check', async (req, res) => {
|
|||||||
logger.error(`[session/check] Error fetching identities: ${error.message}`);
|
logger.error(`[session/check] Error fetching identities: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, нужно ли создать новый гостевой ID
|
// Проверяем, нужно ли создать новый гостевой ID
|
||||||
if (!authenticated && !req.session.guestId) {
|
if (!authenticated && !req.session.guestId) {
|
||||||
req.session.guestId = crypto.randomBytes(16).toString('hex');
|
req.session.guestId = crypto.randomBytes(16).toString('hex');
|
||||||
|
|
||||||
// Сохраняем сессию с новым гостевым ID
|
// Сохраняем сессию с новым гостевым ID
|
||||||
await sessionService.saveSession(req.session);
|
await sessionService.saveSession(req.session);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Формируем ответ
|
// Формируем ответ
|
||||||
const response = {
|
const response = {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -539,9 +546,9 @@ router.get('/check', async (req, res) => {
|
|||||||
guestId: req.session.guestId || null,
|
guestId: req.session.guestId || null,
|
||||||
authType,
|
authType,
|
||||||
identitiesCount: identities.length,
|
identitiesCount: identities.length,
|
||||||
isAdmin: isAdmin || false
|
isAdmin: isAdmin || false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Добавляем специфические поля в зависимости от типа аутентификации
|
// Добавляем специфические поля в зависимости от типа аутентификации
|
||||||
if (authType === 'wallet') {
|
if (authType === 'wallet') {
|
||||||
response.address = req.session.address || null;
|
response.address = req.session.address || null;
|
||||||
@@ -556,13 +563,13 @@ router.get('/check', async (req, res) => {
|
|||||||
response.telegramFirstName = req.session.telegramFirstName;
|
response.telegramFirstName = req.session.telegramFirstName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json(response);
|
return res.json(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[session/check] Error:', error);
|
logger.error('[session/check] Error:', error);
|
||||||
return res.status(500).json({
|
return res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Internal server error'
|
error: 'Internal server error',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -609,7 +616,7 @@ router.get('/check-access', requireAuth, async (req, res) => {
|
|||||||
|
|
||||||
if (address) {
|
if (address) {
|
||||||
const isAdmin = await authService.checkAdminTokens(address);
|
const isAdmin = await authService.checkAdminTokens(address);
|
||||||
|
|
||||||
// Обновляем сессию
|
// Обновляем сессию
|
||||||
req.session.isAdmin = isAdmin;
|
req.session.isAdmin = isAdmin;
|
||||||
await sessionService.saveSession(req.session);
|
await sessionService.saveSession(req.session);
|
||||||
@@ -618,7 +625,7 @@ router.get('/check-access', requireAuth, async (req, res) => {
|
|||||||
success: true,
|
success: true,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
userId,
|
userId,
|
||||||
address
|
address,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -626,9 +633,8 @@ router.get('/check-access', requireAuth, async (req, res) => {
|
|||||||
success: true,
|
success: true,
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
userId,
|
userId,
|
||||||
address: null
|
address: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error checking access:', error);
|
logger.error('Error checking access:', error);
|
||||||
return res.status(500).json({ error: 'Internal server error' });
|
return res.status(500).json({ error: 'Internal server error' });
|
||||||
@@ -639,22 +645,22 @@ router.get('/check-access', requireAuth, async (req, res) => {
|
|||||||
router.post('/refresh-session', async (req, res) => {
|
router.post('/refresh-session', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { address } = req.body;
|
const { address } = req.body;
|
||||||
|
|
||||||
if (req.session && req.session.authenticated) {
|
if (req.session && req.session.authenticated) {
|
||||||
logger.info('Обновление сессии для пользователя:', req.session.userId);
|
logger.info('Обновление сессии для пользователя:', req.session.userId);
|
||||||
|
|
||||||
// Обновляем время жизни сессии
|
// Обновляем время жизни сессии
|
||||||
req.session.cookie.maxAge = 30 * 24 * 60 * 60 * 1000; // 30 дней
|
req.session.cookie.maxAge = 30 * 24 * 60 * 60 * 1000; // 30 дней
|
||||||
|
|
||||||
// Сохраняем обновленную сессию
|
// Сохраняем обновленную сессию
|
||||||
await sessionService.saveSession(req.session);
|
await sessionService.saveSession(req.session);
|
||||||
|
|
||||||
return res.json({ success: true });
|
return res.json({ success: true });
|
||||||
} else if (address) {
|
} else if (address) {
|
||||||
// Если сессия не аутентифицирована, но есть адрес
|
// Если сессия не аутентифицирована, но есть адрес
|
||||||
try {
|
try {
|
||||||
const user = await identityService.findUserByIdentity('wallet', address.toLowerCase());
|
const user = await identityService.findUserByIdentity('wallet', address.toLowerCase());
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
// Обновляем сессию
|
// Обновляем сессию
|
||||||
req.session.authenticated = true;
|
req.session.authenticated = true;
|
||||||
@@ -662,17 +668,17 @@ router.post('/refresh-session', async (req, res) => {
|
|||||||
req.session.address = address.toLowerCase();
|
req.session.address = address.toLowerCase();
|
||||||
req.session.isAdmin = user.role === 'admin';
|
req.session.isAdmin = user.role === 'admin';
|
||||||
req.session.authType = 'wallet';
|
req.session.authType = 'wallet';
|
||||||
|
|
||||||
// Сохраняем обновленную сессию
|
// Сохраняем обновленную сессию
|
||||||
await sessionService.saveSession(req.session);
|
await sessionService.saveSession(req.session);
|
||||||
|
|
||||||
return res.json({ success: true });
|
return res.json({ success: true });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Ошибка при проверке пользователя:', error);
|
logger.error('Ошибка при проверке пользователя:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если не удалось обновить сессию, возвращаем успех=false, но не ошибку
|
// Если не удалось обновить сессию, возвращаем успех=false, но не ошибку
|
||||||
return res.json({ success: false });
|
return res.json({ success: false });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -685,82 +691,78 @@ router.post('/refresh-session', async (req, res) => {
|
|||||||
router.post('/wallet', async (req, res) => {
|
router.post('/wallet', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { address, nonce, signature } = req.body;
|
const { address, nonce, signature } = req.body;
|
||||||
|
|
||||||
if (!address || !nonce || !signature) {
|
if (!address || !nonce || !signature) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Missing required fields'
|
error: 'Missing required fields',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем гостевые ID до аутентификации
|
// Сохраняем гостевые ID до аутентификации
|
||||||
const guestId = req.session.guestId;
|
const guestId = req.session.guestId;
|
||||||
const previousGuestId = req.session.previousGuestId;
|
const previousGuestId = req.session.previousGuestId;
|
||||||
|
|
||||||
// Формируем сообщение для проверки
|
// Формируем сообщение для проверки
|
||||||
const message = `Sign this message to authenticate with HB3 DApp: ${nonce}`;
|
const message = `Sign this message to authenticate with HB3 DApp: ${nonce}`;
|
||||||
|
|
||||||
// Проверяем подпись
|
// Проверяем подпись
|
||||||
const validSignature = await authService.verifySignature(message, signature, address);
|
const validSignature = await authService.verifySignature(message, signature, address);
|
||||||
if (!validSignature) {
|
if (!validSignature) {
|
||||||
return res.status(401).json({
|
return res.status(401).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Invalid signature'
|
error: 'Invalid signature',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем или создаем пользователя
|
// Получаем или создаем пользователя
|
||||||
const { userId } = await authService.findOrCreateUser(address);
|
const { userId } = await authService.findOrCreateUser(address);
|
||||||
|
|
||||||
// Проверяем наличие админских токенов
|
// Проверяем наличие админских токенов
|
||||||
const isAdmin = await authService.checkAdminTokens(address);
|
const isAdmin = await authService.checkAdminTokens(address);
|
||||||
|
|
||||||
// Обновляем роль пользователя в базе данных, если нужно
|
// Обновляем роль пользователя в базе данных, если нужно
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
await db.query(
|
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
|
||||||
'UPDATE users SET role = $1 WHERE id = $2',
|
|
||||||
['admin', userId]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем идентификаторы
|
// Сохраняем идентификаторы
|
||||||
await identityService.saveIdentity(userId, 'wallet', address.toLowerCase(), true);
|
await identityService.saveIdentity(userId, 'wallet', address.toLowerCase(), true);
|
||||||
|
|
||||||
if (guestId) {
|
if (guestId) {
|
||||||
await identityService.saveIdentity(userId, 'guest', guestId, true);
|
await identityService.saveIdentity(userId, 'guest', guestId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (previousGuestId && previousGuestId !== guestId) {
|
if (previousGuestId && previousGuestId !== guestId) {
|
||||||
await identityService.saveIdentity(userId, 'guest', previousGuestId, true);
|
await identityService.saveIdentity(userId, 'guest', previousGuestId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Устанавливаем сессию
|
// Устанавливаем сессию
|
||||||
req.session.userId = userId;
|
req.session.userId = userId;
|
||||||
req.session.address = address.toLowerCase();
|
req.session.address = address.toLowerCase();
|
||||||
req.session.authType = 'wallet';
|
req.session.authType = 'wallet';
|
||||||
req.session.authenticated = true;
|
req.session.authenticated = true;
|
||||||
req.session.isAdmin = isAdmin;
|
req.session.isAdmin = isAdmin;
|
||||||
|
|
||||||
// Сохраняем сессию
|
// Сохраняем сессию
|
||||||
await sessionService.saveSession(req.session);
|
await sessionService.saveSession(req.session);
|
||||||
|
|
||||||
// Связываем гостевые сообщения с пользователем
|
// Связываем гостевые сообщения с пользователем
|
||||||
await sessionService.linkGuestMessages(req.session, userId);
|
await sessionService.linkGuestMessages(req.session, userId);
|
||||||
|
|
||||||
// Возвращаем успешный ответ
|
// Возвращаем успешный ответ
|
||||||
return res.json({
|
return res.json({
|
||||||
success: true,
|
success: true,
|
||||||
userId,
|
userId,
|
||||||
address,
|
address,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
authenticated: true
|
authenticated: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[wallet] Error:', error);
|
logger.error('[wallet] Error:', error);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Server error during wallet authentication'
|
error: 'Server error during wallet authentication',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -769,19 +771,19 @@ router.post('/wallet', async (req, res) => {
|
|||||||
router.get('/identities', requireAuth, async (req, res) => {
|
router.get('/identities', requireAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { userId } = req.session;
|
const { userId } = req.session;
|
||||||
|
|
||||||
// Получаем все идентификаторы пользователя
|
// Получаем все идентификаторы пользователя
|
||||||
const identities = await identityService.getUserIdentities(userId);
|
const identities = await identityService.getUserIdentities(userId);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
identities
|
identities,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error getting user identities:', error);
|
logger.error('Error getting user identities:', error);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Internal server error'
|
error: 'Internal server error',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -794,17 +796,17 @@ router.get('/check-session', async (req, res) => {
|
|||||||
req.session.guestId = crypto.randomBytes(16).toString('hex');
|
req.session.guestId = crypto.randomBytes(16).toString('hex');
|
||||||
await sessionService.saveSession(req.session);
|
await sessionService.saveSession(req.session);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
guestId: req.session.guestId,
|
guestId: req.session.guestId,
|
||||||
isAuthenticated: req.session.authenticated || false
|
isAuthenticated: req.session.authenticated || false,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error checking session:', error);
|
logger.error('Error checking session:', error);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Internal server error'
|
error: 'Internal server error',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -813,21 +815,21 @@ router.get('/check-session', async (req, res) => {
|
|||||||
router.get('/check-tokens/:address', async (req, res) => {
|
router.get('/check-tokens/:address', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { address } = req.params;
|
const { address } = req.params;
|
||||||
|
|
||||||
// Получаем балансы токенов на всех сетях
|
// Получаем балансы токенов на всех сетях
|
||||||
const balances = await authService.getTokenBalances(address);
|
const balances = await authService.getTokenBalances(address);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
balances
|
balances,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error checking token balances:', error);
|
logger.error('Error checking token balances:', error);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Internal server error'
|
error: 'Internal server error',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -12,19 +12,19 @@ const { v4: uuidv4 } = require('uuid');
|
|||||||
async function processGuestMessages(userId, guestId) {
|
async function processGuestMessages(userId, guestId) {
|
||||||
try {
|
try {
|
||||||
console.log(`Processing guest messages for user ${userId} with guest ID ${guestId}`);
|
console.log(`Processing guest messages for user ${userId} with guest ID ${guestId}`);
|
||||||
|
|
||||||
// Проверяем, обрабатывались ли уже эти сообщения
|
// Проверяем, обрабатывались ли уже эти сообщения
|
||||||
const mappingCheck = await db.query(
|
const mappingCheck = await db.query(
|
||||||
'SELECT processed FROM guest_user_mapping WHERE guest_id = $1',
|
'SELECT processed FROM guest_user_mapping WHERE guest_id = $1',
|
||||||
[guestId]
|
[guestId]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Если сообщения уже обработаны, пропускаем
|
// Если сообщения уже обработаны, пропускаем
|
||||||
if (mappingCheck.rows.length > 0 && mappingCheck.rows[0].processed) {
|
if (mappingCheck.rows.length > 0 && mappingCheck.rows[0].processed) {
|
||||||
console.log(`Guest messages for guest ID ${guestId} were already processed.`);
|
console.log(`Guest messages for guest ID ${guestId} were already processed.`);
|
||||||
return { success: true, message: 'Guest messages already processed' };
|
return { success: true, message: 'Guest messages already processed' };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем наличие mapping записи и создаем если нет
|
// Проверяем наличие mapping записи и создаем если нет
|
||||||
if (mappingCheck.rows.length === 0) {
|
if (mappingCheck.rows.length === 0) {
|
||||||
await db.query(
|
await db.query(
|
||||||
@@ -33,49 +33,49 @@ async function processGuestMessages(userId, guestId) {
|
|||||||
);
|
);
|
||||||
console.log(`Created mapping for guest ID ${guestId} to user ${userId}`);
|
console.log(`Created mapping for guest ID ${guestId} to user ${userId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем все гостевые сообщения
|
// Получаем все гостевые сообщения
|
||||||
const guestMessagesResult = await db.query(
|
const guestMessagesResult = await db.query(
|
||||||
'SELECT * FROM guest_messages WHERE guest_id = $1 ORDER BY created_at ASC',
|
'SELECT * FROM guest_messages WHERE guest_id = $1 ORDER BY created_at ASC',
|
||||||
[guestId]
|
[guestId]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (guestMessagesResult.rows.length === 0) {
|
if (guestMessagesResult.rows.length === 0) {
|
||||||
console.log('No guest messages found');
|
console.log('No guest messages found');
|
||||||
|
|
||||||
// Помечаем как обработанные, даже если сообщений нет
|
// Помечаем как обработанные, даже если сообщений нет
|
||||||
await db.query(
|
await db.query('UPDATE guest_user_mapping SET processed = true WHERE guest_id = $1', [
|
||||||
'UPDATE guest_user_mapping SET processed = true WHERE guest_id = $1',
|
guestId,
|
||||||
[guestId]
|
]);
|
||||||
);
|
|
||||||
|
|
||||||
return { success: true, message: 'No guest messages found' };
|
return { success: true, message: 'No guest messages found' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const guestMessages = guestMessagesResult.rows;
|
const guestMessages = guestMessagesResult.rows;
|
||||||
console.log(`Found ${guestMessages.length} guest messages`);
|
console.log(`Found ${guestMessages.length} guest messages`);
|
||||||
|
|
||||||
// Создаем новый диалог для этих сообщений
|
// Создаем новый диалог для этих сообщений
|
||||||
const firstMessage = guestMessages[0];
|
const firstMessage = guestMessages[0];
|
||||||
const title = firstMessage.content.length > 30
|
const title =
|
||||||
? `${firstMessage.content.substring(0, 30)}...`
|
firstMessage.content.length > 30
|
||||||
: firstMessage.content;
|
? `${firstMessage.content.substring(0, 30)}...`
|
||||||
|
: firstMessage.content;
|
||||||
|
|
||||||
const newConversationResult = await db.query(
|
const newConversationResult = await db.query(
|
||||||
'INSERT INTO conversations (user_id, title) VALUES ($1, $2) RETURNING *',
|
'INSERT INTO conversations (user_id, title) VALUES ($1, $2) RETURNING *',
|
||||||
[userId, title]
|
[userId, title]
|
||||||
);
|
);
|
||||||
|
|
||||||
const conversation = newConversationResult.rows[0];
|
const conversation = newConversationResult.rows[0];
|
||||||
console.log('Created new conversation for guest messages:', conversation);
|
console.log('Created new conversation for guest messages:', conversation);
|
||||||
|
|
||||||
// Отслеживаем успешные сохранения сообщений
|
// Отслеживаем успешные сохранения сообщений
|
||||||
const savedMessageIds = [];
|
const savedMessageIds = [];
|
||||||
|
|
||||||
// Обрабатываем каждое гостевое сообщение
|
// Обрабатываем каждое гостевое сообщение
|
||||||
for (const guestMessage of guestMessages) {
|
for (const guestMessage of guestMessages) {
|
||||||
console.log(`Processing guest message ID ${guestMessage.id}: ${guestMessage.content}`);
|
console.log(`Processing guest message ID ${guestMessage.id}: ${guestMessage.content}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Сохраняем сообщение пользователя
|
// Сохраняем сообщение пользователя
|
||||||
const userMessageResult = await db.query(
|
const userMessageResult = await db.query(
|
||||||
@@ -85,26 +85,26 @@ async function processGuestMessages(userId, guestId) {
|
|||||||
($1, $2, $3, $4, $5, $6, $7)
|
($1, $2, $3, $4, $5, $6, $7)
|
||||||
RETURNING *`,
|
RETURNING *`,
|
||||||
[
|
[
|
||||||
conversation.id,
|
conversation.id,
|
||||||
guestMessage.content,
|
guestMessage.content,
|
||||||
'user',
|
'user',
|
||||||
'user',
|
'user',
|
||||||
'web',
|
'web',
|
||||||
guestMessage.created_at,
|
guestMessage.created_at,
|
||||||
userId // Добавляем userId в сообщение для прямой связи
|
userId, // Добавляем userId в сообщение для прямой связи
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`Saved user message with ID ${userMessageResult.rows[0].id}`);
|
console.log(`Saved user message with ID ${userMessageResult.rows[0].id}`);
|
||||||
savedMessageIds.push(guestMessage.id);
|
savedMessageIds.push(guestMessage.id);
|
||||||
|
|
||||||
// Получаем ответ от ИИ только для сообщений пользователя (не AI)
|
// Получаем ответ от ИИ только для сообщений пользователя (не AI)
|
||||||
if (!guestMessage.is_ai) {
|
if (!guestMessage.is_ai) {
|
||||||
console.log('Getting AI response for:', guestMessage.content);
|
console.log('Getting AI response for:', guestMessage.content);
|
||||||
const language = guestMessage.language || 'auto';
|
const language = guestMessage.language || 'auto';
|
||||||
const aiResponse = await aiAssistant.getResponse(guestMessage.content, language);
|
const aiResponse = await aiAssistant.getResponse(guestMessage.content, language);
|
||||||
console.log('AI response received:', aiResponse);
|
console.log('AI response received:', aiResponse);
|
||||||
|
|
||||||
// Сохраняем ответ от ИИ
|
// Сохраняем ответ от ИИ
|
||||||
const aiMessageResult = await db.query(
|
const aiMessageResult = await db.query(
|
||||||
`INSERT INTO messages
|
`INSERT INTO messages
|
||||||
@@ -113,16 +113,16 @@ async function processGuestMessages(userId, guestId) {
|
|||||||
($1, $2, $3, $4, $5, $6, $7)
|
($1, $2, $3, $4, $5, $6, $7)
|
||||||
RETURNING *`,
|
RETURNING *`,
|
||||||
[
|
[
|
||||||
conversation.id,
|
conversation.id,
|
||||||
aiResponse,
|
aiResponse,
|
||||||
'assistant',
|
'assistant',
|
||||||
'assistant',
|
'assistant',
|
||||||
'web',
|
'web',
|
||||||
new Date(),
|
new Date(),
|
||||||
userId // Добавляем userId в сообщение для прямой связи
|
userId, // Добавляем userId в сообщение для прямой связи
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`Saved AI response with ID ${aiMessageResult.rows[0].id}`);
|
console.log(`Saved AI response with ID ${aiMessageResult.rows[0].id}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -130,25 +130,26 @@ async function processGuestMessages(userId, guestId) {
|
|||||||
// Продолжаем с другими сообщениями в случае ошибки
|
// Продолжаем с другими сообщениями в случае ошибки
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Удаляем только успешно обработанные гостевые сообщения
|
// Удаляем только успешно обработанные гостевые сообщения
|
||||||
if (savedMessageIds.length > 0) {
|
if (savedMessageIds.length > 0) {
|
||||||
await db.query('DELETE FROM guest_messages WHERE id = ANY($1)', [savedMessageIds]);
|
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]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Помечаем гостевой ID как обработанный
|
||||||
|
await db.query('UPDATE guest_user_mapping SET processed = true WHERE guest_id = $1', [
|
||||||
|
guestId,
|
||||||
|
]);
|
||||||
} else {
|
} else {
|
||||||
console.log('No guest messages were successfully processed, skipping deletion');
|
console.log('No guest messages were successfully processed, skipping deletion');
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: `Processed ${savedMessageIds.length} of ${guestMessages.length} guest messages`,
|
message: `Processed ${savedMessageIds.length} of ${guestMessages.length} guest messages`,
|
||||||
conversationId: conversation.id
|
conversationId: conversation.id,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error processing guest messages:', error);
|
console.error('Error processing guest messages:', error);
|
||||||
@@ -160,11 +161,11 @@ async function processGuestMessages(userId, guestId) {
|
|||||||
router.post('/guest-message', async (req, res) => {
|
router.post('/guest-message', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { content, language, guestId: requestGuestId } = req.body;
|
const { content, language, guestId: requestGuestId } = req.body;
|
||||||
|
|
||||||
if (!content) {
|
if (!content) {
|
||||||
return res.status(400).json({ success: false, error: 'Content is required' });
|
return res.status(400).json({ success: false, error: 'Content is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Используем гостевой ID из запроса или из сессии, или генерируем новый
|
// Используем гостевой ID из запроса или из сессии, или генерируем новый
|
||||||
const guestId = requestGuestId || req.session.guestId || crypto.randomBytes(16).toString('hex');
|
const guestId = requestGuestId || req.session.guestId || crypto.randomBytes(16).toString('hex');
|
||||||
|
|
||||||
@@ -182,9 +183,9 @@ router.post('/guest-message', async (req, res) => {
|
|||||||
|
|
||||||
console.log('Guest message saved:', result.rows[0]);
|
console.log('Guest message saved:', result.rows[0]);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
messageId: result.rows[0].id
|
messageId: result.rows[0].id,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving guest message:', error);
|
console.error('Error saving guest message:', error);
|
||||||
@@ -195,43 +196,48 @@ router.post('/guest-message', async (req, res) => {
|
|||||||
// Маршрут для обычных сообщений (для аутентифицированных пользователей)
|
// Маршрут для обычных сообщений (для аутентифицированных пользователей)
|
||||||
router.post('/message', requireAuth, async (req, res) => {
|
router.post('/message', requireAuth, async (req, res) => {
|
||||||
const { message, conversationId, language = 'auto' } = req.body;
|
const { message, conversationId, language = 'auto' } = req.body;
|
||||||
|
|
||||||
if (!message) {
|
if (!message) {
|
||||||
return res.status(400).json({ error: 'Message is required' });
|
return res.status(400).json({ error: 'Message is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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;
|
const userId = req.session.userId;
|
||||||
|
|
||||||
let conversation;
|
let conversation;
|
||||||
|
|
||||||
// Если указан ID диалога, проверяем его существование и принадлежность пользователю
|
// Если указан ID диалога, проверяем его существование и принадлежность пользователю
|
||||||
if (conversationId) {
|
if (conversationId) {
|
||||||
const conversationResult = await db.query(
|
const conversationResult = await db.query(
|
||||||
'SELECT * FROM conversations WHERE id = $1 AND user_id = $2',
|
'SELECT * FROM conversations WHERE id = $1 AND user_id = $2',
|
||||||
[conversationId, userId]
|
[conversationId, userId]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (conversationResult.rows.length === 0) {
|
if (conversationResult.rows.length === 0) {
|
||||||
return res.status(404).json({ error: 'Conversation not found or access denied' });
|
return res.status(404).json({ error: 'Conversation not found or access denied' });
|
||||||
}
|
}
|
||||||
|
|
||||||
conversation = conversationResult.rows[0];
|
conversation = conversationResult.rows[0];
|
||||||
console.log('Using existing conversation:', conversation);
|
console.log('Using existing conversation:', conversation);
|
||||||
} else {
|
} else {
|
||||||
// Создаем новый диалог
|
// Создаем новый диалог
|
||||||
const title = message.length > 30 ? `${message.substring(0, 30)}...` : message;
|
const title = message.length > 30 ? `${message.substring(0, 30)}...` : message;
|
||||||
|
|
||||||
const newConversationResult = await db.query(
|
const newConversationResult = await db.query(
|
||||||
'INSERT INTO conversations (user_id, title) VALUES ($1, $2) RETURNING *',
|
'INSERT INTO conversations (user_id, title) VALUES ($1, $2) RETURNING *',
|
||||||
[userId, title]
|
[userId, title]
|
||||||
);
|
);
|
||||||
|
|
||||||
conversation = newConversationResult.rows[0];
|
conversation = newConversationResult.rows[0];
|
||||||
console.log('Created new conversation:', conversation);
|
console.log('Created new conversation:', conversation);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем сообщение пользователя
|
// Сохраняем сообщение пользователя
|
||||||
console.log('Saving user message');
|
console.log('Saving user message');
|
||||||
const userMessageResult = await db.query(
|
const userMessageResult = await db.query(
|
||||||
@@ -242,12 +248,12 @@ router.post('/message', requireAuth, async (req, res) => {
|
|||||||
RETURNING *`,
|
RETURNING *`,
|
||||||
[conversation.id, message, 'user', 'user', 0, 'web', new Date()]
|
[conversation.id, message, 'user', 'user', 0, 'web', new Date()]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Получаем ответ от ИИ
|
// Получаем ответ от ИИ
|
||||||
console.log('Getting AI response');
|
console.log('Getting AI response');
|
||||||
const aiResponse = await aiAssistant.getResponse(message, language);
|
const aiResponse = await aiAssistant.getResponse(message, language);
|
||||||
console.log('AI response received:', aiResponse);
|
console.log('AI response received:', aiResponse);
|
||||||
|
|
||||||
// Сохраняем ответ от ИИ
|
// Сохраняем ответ от ИИ
|
||||||
console.log('Saving AI response');
|
console.log('Saving AI response');
|
||||||
const aiMessageResult = await db.query(
|
const aiMessageResult = await db.query(
|
||||||
@@ -258,14 +264,14 @@ router.post('/message', requireAuth, async (req, res) => {
|
|||||||
RETURNING *`,
|
RETURNING *`,
|
||||||
[conversation.id, aiResponse, 'assistant', 'assistant', 0, 'web', new Date()]
|
[conversation.id, aiResponse, 'assistant', 'assistant', 0, 'web', new Date()]
|
||||||
);
|
);
|
||||||
|
|
||||||
const response = {
|
const response = {
|
||||||
success: true,
|
success: true,
|
||||||
userMessage: userMessageResult.rows[0],
|
userMessage: userMessageResult.rows[0],
|
||||||
aiMessage: aiMessageResult.rows[0],
|
aiMessage: aiMessageResult.rows[0],
|
||||||
conversation
|
conversation,
|
||||||
};
|
};
|
||||||
|
|
||||||
res.json(response);
|
res.json(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error processing message:', error);
|
console.error('Error processing message:', error);
|
||||||
@@ -296,9 +302,9 @@ router.get('/history', async (req, res) => {
|
|||||||
userId: req.session.userId,
|
userId: req.session.userId,
|
||||||
address: req.session.address,
|
address: req.session.address,
|
||||||
authenticated: req.session.authenticated,
|
authenticated: req.session.authenticated,
|
||||||
guestId: req.session.guestId
|
guestId: req.session.guestId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const limit = parseInt(req.query.limit) || 50;
|
const limit = parseInt(req.query.limit) || 50;
|
||||||
const offset = parseInt(req.query.offset) || 0;
|
const offset = parseInt(req.query.offset) || 0;
|
||||||
|
|
||||||
@@ -308,11 +314,11 @@ router.get('/history', async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
console.log('Automatically linking guest messages before fetching history');
|
console.log('Automatically linking guest messages before fetching history');
|
||||||
await processGuestMessages(req.session.userId, req.session.guestId);
|
await processGuestMessages(req.session.userId, req.session.guestId);
|
||||||
|
|
||||||
// Очищаем guestId из сессии после связывания
|
// Очищаем guestId из сессии после связывания
|
||||||
req.session.guestId = null;
|
req.session.guestId = null;
|
||||||
await req.session.save();
|
await req.session.save();
|
||||||
|
|
||||||
console.log('Guest messages automatically linked');
|
console.log('Guest messages automatically linked');
|
||||||
} catch (linkError) {
|
} catch (linkError) {
|
||||||
console.error('Error auto-linking guest messages:', linkError);
|
console.error('Error auto-linking guest messages:', linkError);
|
||||||
@@ -349,17 +355,16 @@ router.get('/history', async (req, res) => {
|
|||||||
LIMIT $2 OFFSET $3`,
|
LIMIT $2 OFFSET $3`,
|
||||||
[req.session.userId, limit, offset]
|
[req.session.userId, limit, offset]
|
||||||
);
|
);
|
||||||
|
|
||||||
messages = result.rows;
|
messages = result.rows;
|
||||||
console.log(`Found ${messages.length} messages for authenticated user`);
|
console.log(`Found ${messages.length} messages for authenticated user`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
success: true,
|
success: true,
|
||||||
messages: messages,
|
messages: messages,
|
||||||
total: total
|
total: total,
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error getting chat history:', error);
|
logger.error('Error getting chat history:', error);
|
||||||
return res.status(500).json({ error: 'Internal server error' });
|
return res.status(500).json({ error: 'Internal server error' });
|
||||||
@@ -368,4 +373,4 @@ router.get('/history', async (req, res) => {
|
|||||||
|
|
||||||
// Экспортируем маршрутизатор и функцию processGuestMessages отдельно
|
// Экспортируем маршрутизатор и функцию processGuestMessages отдельно
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
module.exports.processGuestMessages = processGuestMessages;
|
module.exports.processGuestMessages = processGuestMessages;
|
||||||
|
|||||||
@@ -26,20 +26,20 @@ router.post('/link', requireAuth, async (req, res) => {
|
|||||||
// Если тип - wallet, сначала проверим, не привязан ли он уже к другому пользователю
|
// Если тип - wallet, сначала проверим, не привязан ли он уже к другому пользователю
|
||||||
if (type === 'wallet') {
|
if (type === 'wallet') {
|
||||||
const normalizedWallet = value.toLowerCase();
|
const normalizedWallet = value.toLowerCase();
|
||||||
|
|
||||||
// Проверяем, существует ли уже такой кошелек
|
// Проверяем, существует ли уже такой кошелек
|
||||||
const existingCheck = await db.query(
|
const existingCheck = await db.query(
|
||||||
`SELECT user_id FROM user_identities
|
`SELECT user_id FROM user_identities
|
||||||
WHERE provider = 'wallet' AND provider_id = $1`,
|
WHERE provider = 'wallet' AND provider_id = $1`,
|
||||||
[normalizedWallet]
|
[normalizedWallet]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (existingCheck.rows.length > 0) {
|
if (existingCheck.rows.length > 0) {
|
||||||
const existingUserId = existingCheck.rows[0].user_id;
|
const existingUserId = existingCheck.rows[0].user_id;
|
||||||
if (existingUserId !== userId) {
|
if (existingUserId !== userId) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: `This wallet (${value}) is already linked to another account`
|
error: `This wallet (${value}) is already linked to another account`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,22 +57,22 @@ router.post('/link', requireAuth, async (req, res) => {
|
|||||||
req.session.email = value;
|
req.session.email = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Identity linked successfully',
|
message: 'Identity linked successfully',
|
||||||
isAdmin: req.session.isAdmin
|
isAdmin: req.session.isAdmin,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error linking identity:', error);
|
logger.error('Error linking identity:', error);
|
||||||
|
|
||||||
// Делаем более понятные сообщения об ошибках
|
// Делаем более понятные сообщения об ошибках
|
||||||
if (error.message && error.message.includes('already belongs to another user')) {
|
if (error.message && error.message.includes('already belongs to another user')) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: `This identity is already linked to another account`
|
error: `This identity is already linked to another account`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(500).json({ error: error.message || 'Internal server error' });
|
res.status(500).json({ error: error.message || 'Internal server error' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -93,13 +93,13 @@ router.get('/token-balances', requireAuth, async (req, res) => {
|
|||||||
|
|
||||||
// Здесь логирование инициирования получения баланса может быть полезно
|
// Здесь логирование инициирования получения баланса может быть полезно
|
||||||
logger.info(`Fetching token balances for user ${userId} with wallet ${wallet}`);
|
logger.info(`Fetching token balances for user ${userId} with wallet ${wallet}`);
|
||||||
|
|
||||||
// Получаем балансы токенов
|
// Получаем балансы токенов
|
||||||
const balances = await authService.getTokenBalances(wallet);
|
const balances = await authService.getTokenBalances(wallet);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
balances
|
balances,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error getting token balances:', error);
|
logger.error('Error getting token balances:', error);
|
||||||
|
|||||||
@@ -8,23 +8,23 @@ const logger = require('../utils/logger');
|
|||||||
router.get('/balances', requireAuth, async (req, res) => {
|
router.get('/balances', requireAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { address } = req.session;
|
const { address } = req.session;
|
||||||
|
|
||||||
if (!address) {
|
if (!address) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: 'No wallet address in session'
|
error: 'No wallet address in session',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Fetching token balances for address: ${address}`);
|
logger.info(`Fetching token balances for address: ${address}`);
|
||||||
const balances = await authService.getTokenBalances(address);
|
const balances = await authService.getTokenBalances(address);
|
||||||
|
|
||||||
res.json(balances);
|
res.json(balances);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error fetching token balances:', error);
|
logger.error('Error fetching token balances:', error);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
error: 'Failed to fetch token balances'
|
error: 'Failed to fetch token balances',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -23,19 +23,16 @@ router.post('/update-language', requireAuth, async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const { language } = req.body;
|
const { language } = req.body;
|
||||||
const userId = req.session.userId;
|
const userId = req.session.userId;
|
||||||
|
|
||||||
// Проверка валидности языка
|
// Проверка валидности языка
|
||||||
const validLanguages = ['ru', 'en'];
|
const validLanguages = ['ru', 'en'];
|
||||||
if (!validLanguages.includes(language)) {
|
if (!validLanguages.includes(language)) {
|
||||||
return res.status(400).json({ error: 'Неподдерживаемый язык' });
|
return res.status(400).json({ error: 'Неподдерживаемый язык' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновление языка в базе данных
|
// Обновление языка в базе данных
|
||||||
await db.query(
|
await db.query('UPDATE users SET preferred_language = $1 WHERE id = $2', [language, userId]);
|
||||||
'UPDATE users SET preferred_language = $1 WHERE id = $2',
|
|
||||||
[language, userId]
|
|
||||||
);
|
|
||||||
|
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error updating language:', error);
|
logger.error('Error updating language:', error);
|
||||||
@@ -48,22 +45,23 @@ router.post('/update-profile', requireAuth, async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const { firstName, lastName } = req.body;
|
const { firstName, lastName } = req.body;
|
||||||
const userId = req.session.userId;
|
const userId = req.session.userId;
|
||||||
|
|
||||||
// Проверка валидности данных
|
// Проверка валидности данных
|
||||||
if (firstName && firstName.length > 255) {
|
if (firstName && firstName.length > 255) {
|
||||||
return res.status(400).json({ error: 'Имя слишком длинное (максимум 255 символов)' });
|
return res.status(400).json({ error: 'Имя слишком длинное (максимум 255 символов)' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastName && lastName.length > 255) {
|
if (lastName && lastName.length > 255) {
|
||||||
return res.status(400).json({ error: 'Фамилия слишком длинная (максимум 255 символов)' });
|
return res.status(400).json({ error: 'Фамилия слишком длинная (максимум 255 символов)' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновление имени и фамилии в базе данных
|
// Обновление имени и фамилии в базе данных
|
||||||
await db.query(
|
await db.query('UPDATE users SET first_name = $1, last_name = $2 WHERE id = $3', [
|
||||||
'UPDATE users SET first_name = $1, last_name = $2 WHERE id = $3',
|
firstName || null,
|
||||||
[firstName || null, lastName || null, userId]
|
lastName || null,
|
||||||
);
|
userId,
|
||||||
|
]);
|
||||||
|
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error updating user profile:', error);
|
logger.error('Error updating user profile:', error);
|
||||||
@@ -75,29 +73,29 @@ router.post('/update-profile', requireAuth, async (req, res) => {
|
|||||||
router.get('/profile/current', requireAuth, async (req, res) => {
|
router.get('/profile/current', requireAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const userId = req.session.userId;
|
const userId = req.session.userId;
|
||||||
|
|
||||||
// Получение данных пользователя
|
// Получение данных пользователя
|
||||||
const userResult = await db.query(
|
const userResult = await db.query(
|
||||||
'SELECT id, username, first_name, last_name, role, status, created_at, preferred_language FROM users WHERE id = $1',
|
'SELECT id, username, first_name, last_name, role, status, created_at, preferred_language FROM users WHERE id = $1',
|
||||||
[userId]
|
[userId]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (userResult.rows.length === 0) {
|
if (userResult.rows.length === 0) {
|
||||||
return res.status(404).json({ error: 'Пользователь не найден' });
|
return res.status(404).json({ error: 'Пользователь не найден' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получение идентификаторов пользователя
|
// Получение идентификаторов пользователя
|
||||||
const identitiesResult = await db.query(
|
const identitiesResult = await db.query(
|
||||||
'SELECT provider, provider_id FROM user_identities WHERE user_id = $1',
|
'SELECT provider, provider_id FROM user_identities WHERE user_id = $1',
|
||||||
[userId]
|
[userId]
|
||||||
);
|
);
|
||||||
|
|
||||||
const user = userResult.rows[0];
|
const user = userResult.rows[0];
|
||||||
const identities = identitiesResult.rows.reduce((acc, identity) => {
|
const identities = identitiesResult.rows.reduce((acc, identity) => {
|
||||||
acc[identity.provider] = identity.provider_id;
|
acc[identity.provider] = identity.provider_id;
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
id: user.id,
|
id: user.id,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
@@ -107,7 +105,7 @@ router.get('/profile/current', requireAuth, async (req, res) => {
|
|||||||
status: user.status,
|
status: user.status,
|
||||||
createdAt: user.created_at,
|
createdAt: user.created_at,
|
||||||
preferredLanguage: user.preferred_language,
|
preferredLanguage: user.preferred_language,
|
||||||
identities
|
identities,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error getting user profile:', error);
|
logger.error('Error getting user profile:', error);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ if (!fs.existsSync(logDir)) {
|
|||||||
|
|
||||||
const logFile = path.join(logDir, 'fix-duplicates.log');
|
const logFile = path.join(logDir, 'fix-duplicates.log');
|
||||||
const logger = {
|
const logger = {
|
||||||
log: message => {
|
log: (message) => {
|
||||||
const timestamp = new Date().toISOString();
|
const timestamp = new Date().toISOString();
|
||||||
const logMessage = `[${timestamp}] ${message}\n`;
|
const logMessage = `[${timestamp}] ${message}\n`;
|
||||||
console.log(message);
|
console.log(message);
|
||||||
@@ -27,7 +27,7 @@ const logger = {
|
|||||||
const logMessage = `[${timestamp}] ERROR: ${message}${errorDetail}\n`;
|
const logMessage = `[${timestamp}] ERROR: ${message}${errorDetail}\n`;
|
||||||
console.error(`ERROR: ${message}${errorDetail}`);
|
console.error(`ERROR: ${message}${errorDetail}`);
|
||||||
fs.appendFileSync(logFile, logMessage);
|
fs.appendFileSync(logFile, logMessage);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Создаем подключение к базе данных
|
// Создаем подключение к базе данных
|
||||||
@@ -54,10 +54,10 @@ function normalizeWalletAddress(address) {
|
|||||||
*/
|
*/
|
||||||
async function findDuplicateWallets() {
|
async function findDuplicateWallets() {
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.log('Поиск дубликатов wallet-идентификаторов...');
|
logger.log('Поиск дубликатов wallet-идентификаторов...');
|
||||||
|
|
||||||
// Находим пары идентификаторов, которые отличаются только регистром
|
// Находим пары идентификаторов, которые отличаются только регистром
|
||||||
const result = await client.query(`
|
const result = await client.query(`
|
||||||
SELECT
|
SELECT
|
||||||
@@ -77,9 +77,9 @@ async function findDuplicateWallets() {
|
|||||||
LOWER(ui1.provider_id) = LOWER(ui2.provider_id) AND
|
LOWER(ui1.provider_id) = LOWER(ui2.provider_id) AND
|
||||||
ui1.provider_id <> ui2.provider_id
|
ui1.provider_id <> ui2.provider_id
|
||||||
`);
|
`);
|
||||||
|
|
||||||
logger.log(`Найдено ${result.rows.length} потенциальных дубликатов wallet-идентификаторов`);
|
logger.log(`Найдено ${result.rows.length} потенциальных дубликатов wallet-идентификаторов`);
|
||||||
|
|
||||||
return result.rows;
|
return result.rows;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Ошибка при поиске дубликатов wallet-идентификаторов', error);
|
logger.error('Ошибка при поиске дубликатов wallet-идентификаторов', error);
|
||||||
@@ -95,46 +95,52 @@ async function findDuplicateWallets() {
|
|||||||
*/
|
*/
|
||||||
async function fixDuplicates(duplicates) {
|
async function fixDuplicates(duplicates) {
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.log('Исправление дубликатов идентификаторов...');
|
logger.log('Исправление дубликатов идентификаторов...');
|
||||||
|
|
||||||
await client.query('BEGIN');
|
await client.query('BEGIN');
|
||||||
|
|
||||||
for (const dup of duplicates) {
|
for (const dup of duplicates) {
|
||||||
// Проверяем, принадлежат ли идентификаторы одному пользователю
|
// Проверяем, принадлежат ли идентификаторы одному пользователю
|
||||||
if (dup.user_id1 === dup.user_id2) {
|
if (dup.user_id1 === dup.user_id2) {
|
||||||
// Если да, удаляем один из дубликатов (не в нижнем регистре)
|
// Если да, удаляем один из дубликатов (не в нижнем регистре)
|
||||||
const normalizedAddress = normalizeWalletAddress(dup.provider_id1);
|
const normalizedAddress = normalizeWalletAddress(dup.provider_id1);
|
||||||
|
|
||||||
// Определяем, какой идентификатор нужно удалить
|
// Определяем, какой идентификатор нужно удалить
|
||||||
const idToDelete = dup.provider_id1 === normalizedAddress ? dup.id2 : dup.id1;
|
const idToDelete = dup.provider_id1 === normalizedAddress ? dup.id2 : dup.id1;
|
||||||
|
|
||||||
logger.log(`Удаление дубликата ID ${idToDelete} для адреса ${normalizedAddress}`);
|
logger.log(`Удаление дубликата ID ${idToDelete} для адреса ${normalizedAddress}`);
|
||||||
|
|
||||||
await client.query('DELETE FROM user_identities WHERE id = $1', [idToDelete]);
|
await client.query('DELETE FROM user_identities WHERE id = $1', [idToDelete]);
|
||||||
|
|
||||||
// Проверяем, что второй идентификатор в нормализованной форме
|
// Проверяем, что второй идентификатор в нормализованной форме
|
||||||
const remainingId = dup.provider_id1 === normalizedAddress ? dup.id1 : dup.id2;
|
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) {
|
if (remainingAddress !== normalizedAddress) {
|
||||||
logger.log(`Обновление идентификатора ID ${remainingId} до нормализованного значения ${normalizedAddress}`);
|
logger.log(
|
||||||
|
`Обновление идентификатора ID ${remainingId} до нормализованного значения ${normalizedAddress}`
|
||||||
await client.query(
|
|
||||||
'UPDATE user_identities SET provider_id = $1 WHERE id = $2',
|
|
||||||
[normalizedAddress, remainingId]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await client.query('UPDATE user_identities SET provider_id = $1 WHERE id = $2', [
|
||||||
|
normalizedAddress,
|
||||||
|
remainingId,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Если идентификаторы принадлежат разным пользователям, нужно решить конфликт
|
// Если идентификаторы принадлежат разным пользователям, нужно решить конфликт
|
||||||
// Для определения какой пользователь является основным, можно использовать:
|
// Для определения какой пользователь является основным, можно использовать:
|
||||||
// 1. Количество сообщений/активности
|
// 1. Количество сообщений/активности
|
||||||
// 2. Дату создания аккаунта
|
// 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
|
SELECT
|
||||||
id,
|
id,
|
||||||
(SELECT COUNT(*) FROM messages WHERE user_id = users.id) as message_count,
|
(SELECT COUNT(*) FROM messages WHERE user_id = users.id) as message_count,
|
||||||
@@ -145,45 +151,55 @@ async function fixDuplicates(duplicates) {
|
|||||||
id IN ($1, $2)
|
id IN ($1, $2)
|
||||||
ORDER BY
|
ORDER BY
|
||||||
message_count DESC, created_at ASC
|
message_count DESC, created_at ASC
|
||||||
`, [dup.user_id1, dup.user_id2]);
|
`,
|
||||||
|
[dup.user_id1, dup.user_id2]
|
||||||
|
);
|
||||||
|
|
||||||
// Если нет пользователей, пропускаем
|
// Если нет пользователей, пропускаем
|
||||||
if (userInfoResult.rows.length === 0) {
|
if (userInfoResult.rows.length === 0) {
|
||||||
logger.log(`Пропуск: не найдены пользователи ${dup.user_id1} и ${dup.user_id2}`);
|
logger.log(`Пропуск: не найдены пользователи ${dup.user_id1} и ${dup.user_id2}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Выбираем первого пользователя как основного (с наибольшим количеством сообщений или самого старого)
|
// Выбираем первого пользователя как основного (с наибольшим количеством сообщений или самого старого)
|
||||||
const mainUserId = userInfoResult.rows[0].id;
|
const mainUserId = userInfoResult.rows[0].id;
|
||||||
const secondaryUserId = mainUserId === dup.user_id1 ? dup.user_id2 : dup.user_id1;
|
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)
|
INSERT INTO user_identities (user_id, provider, provider_id)
|
||||||
SELECT $1, provider, provider_id
|
SELECT $1, provider, provider_id
|
||||||
FROM user_identities
|
FROM user_identities
|
||||||
WHERE user_id = $2
|
WHERE user_id = $2
|
||||||
ON CONFLICT DO NOTHING
|
ON CONFLICT DO NOTHING
|
||||||
`, [mainUserId, secondaryUserId]);
|
`,
|
||||||
|
[mainUserId, secondaryUserId]
|
||||||
|
);
|
||||||
|
|
||||||
// Переносим сообщения
|
// Переносим сообщения
|
||||||
await client.query(`
|
await client.query(
|
||||||
|
`
|
||||||
UPDATE messages
|
UPDATE messages
|
||||||
SET user_id = $1
|
SET user_id = $1
|
||||||
WHERE user_id = $2
|
WHERE user_id = $2
|
||||||
`, [mainUserId, secondaryUserId]);
|
`,
|
||||||
|
[mainUserId, secondaryUserId]
|
||||||
|
);
|
||||||
|
|
||||||
// Переносим другие связанные данные...
|
// Переносим другие связанные данные...
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
// Удаляем вторичного пользователя
|
// Удаляем вторичного пользователя
|
||||||
await client.query('DELETE FROM user_identities WHERE user_id = $1', [secondaryUserId]);
|
await client.query('DELETE FROM user_identities WHERE user_id = $1', [secondaryUserId]);
|
||||||
await client.query('DELETE FROM users WHERE id = $1', [secondaryUserId]);
|
await client.query('DELETE FROM users WHERE id = $1', [secondaryUserId]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.query('COMMIT');
|
await client.query('COMMIT');
|
||||||
logger.log('Исправление дубликатов успешно завершено');
|
logger.log('Исправление дубликатов успешно завершено');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -201,43 +217,43 @@ async function fixDuplicates(duplicates) {
|
|||||||
async function main() {
|
async function main() {
|
||||||
try {
|
try {
|
||||||
logger.log('Запуск скрипта исправления дубликатов идентификаторов...');
|
logger.log('Запуск скрипта исправления дубликатов идентификаторов...');
|
||||||
|
|
||||||
// Шаг 1: Нормализация всех адресов кошельков (приведение к нижнему регистру)
|
// Шаг 1: Нормализация всех адресов кошельков (приведение к нижнему регистру)
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.log('Нормализация всех существующих адресов кошельков...');
|
logger.log('Нормализация всех существующих адресов кошельков...');
|
||||||
|
|
||||||
await client.query('BEGIN');
|
await client.query('BEGIN');
|
||||||
|
|
||||||
// Получаем все идентификаторы кошельков
|
// Получаем все идентификаторы кошельков
|
||||||
const walletsResult = await client.query(`
|
const walletsResult = await client.query(`
|
||||||
SELECT id, provider_id
|
SELECT id, provider_id
|
||||||
FROM user_identities
|
FROM user_identities
|
||||||
WHERE provider = 'wallet'
|
WHERE provider = 'wallet'
|
||||||
`);
|
`);
|
||||||
|
|
||||||
logger.log(`Найдено ${walletsResult.rows.length} идентификаторов кошельков`);
|
logger.log(`Найдено ${walletsResult.rows.length} идентификаторов кошельков`);
|
||||||
|
|
||||||
// Обновляем каждый адрес к нормализованной форме
|
// Обновляем каждый адрес к нормализованной форме
|
||||||
let updatedCount = 0;
|
let updatedCount = 0;
|
||||||
|
|
||||||
for (const wallet of walletsResult.rows) {
|
for (const wallet of walletsResult.rows) {
|
||||||
try {
|
try {
|
||||||
const normalizedAddress = normalizeWalletAddress(wallet.provider_id);
|
const normalizedAddress = normalizeWalletAddress(wallet.provider_id);
|
||||||
|
|
||||||
if (normalizedAddress !== wallet.provider_id) {
|
if (normalizedAddress !== wallet.provider_id) {
|
||||||
await client.query(
|
await client.query('UPDATE user_identities SET provider_id = $1 WHERE id = $2', [
|
||||||
'UPDATE user_identities SET provider_id = $1 WHERE id = $2',
|
normalizedAddress,
|
||||||
[normalizedAddress, wallet.id]
|
wallet.id,
|
||||||
);
|
]);
|
||||||
updatedCount++;
|
updatedCount++;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Ошибка при нормализации адреса ${wallet.provider_id}`, error);
|
logger.error(`Ошибка при нормализации адреса ${wallet.provider_id}`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.query('COMMIT');
|
await client.query('COMMIT');
|
||||||
logger.log(`Нормализовано ${updatedCount} адресов кошельков`);
|
logger.log(`Нормализовано ${updatedCount} адресов кошельков`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -246,16 +262,16 @@ async function main() {
|
|||||||
} finally {
|
} finally {
|
||||||
client.release();
|
client.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Шаг 2: Поиск и исправление дубликатов
|
// Шаг 2: Поиск и исправление дубликатов
|
||||||
const duplicates = await findDuplicateWallets();
|
const duplicates = await findDuplicateWallets();
|
||||||
|
|
||||||
if (duplicates.length > 0) {
|
if (duplicates.length > 0) {
|
||||||
await fixDuplicates(duplicates);
|
await fixDuplicates(duplicates);
|
||||||
} else {
|
} else {
|
||||||
logger.log('Дубликатов wallet-идентификаторов не найдено');
|
logger.log('Дубликатов wallet-идентификаторов не найдено');
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log('Скрипт успешно завершил работу');
|
logger.log('Скрипт успешно завершил работу');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Критическая ошибка при выполнении скрипта', error);
|
logger.error('Критическая ошибка при выполнении скрипта', error);
|
||||||
@@ -265,4 +281,4 @@ async function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Запускаем скрипт
|
// Запускаем скрипт
|
||||||
main();
|
main();
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ async function runMigrations() {
|
|||||||
|
|
||||||
// Получаем список выполненных миграций
|
// Получаем список выполненных миграций
|
||||||
const { rows } = await pool.query('SELECT name FROM migrations');
|
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');
|
const migrationsDir = path.join(__dirname, '../db/migrations');
|
||||||
@@ -27,7 +27,7 @@ async function runMigrations() {
|
|||||||
|
|
||||||
// Сортируем файлы по номеру
|
// Сортируем файлы по номеру
|
||||||
const migrationFiles = files
|
const migrationFiles = files
|
||||||
.filter(f => f.endsWith('.sql'))
|
.filter((f) => f.endsWith('.sql'))
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
const numA = parseInt(a.split('_')[0]);
|
const numA = parseInt(a.split('_')[0]);
|
||||||
const numB = parseInt(b.split('_')[0]);
|
const numB = parseInt(b.split('_')[0]);
|
||||||
@@ -55,14 +55,19 @@ async function runMigrations() {
|
|||||||
|
|
||||||
// Выполняем SQL-функции
|
// Выполняем SQL-функции
|
||||||
const functionsDir = path.join(migrationsDir, 'functions');
|
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);
|
const functionFiles = await fs.readdir(functionsDir);
|
||||||
|
|
||||||
for (const file of functionFiles) {
|
for (const file of functionFiles) {
|
||||||
if (file.endsWith('.sql')) {
|
if (file.endsWith('.sql')) {
|
||||||
const filePath = path.join(functionsDir, file);
|
const filePath = path.join(functionsDir, file);
|
||||||
const sql = await fs.readFile(filePath, 'utf-8');
|
const sql = await fs.readFile(filePath, 'utf-8');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await pool.query(sql);
|
await pool.query(sql);
|
||||||
logger.info(`Function ${file} executed successfully`);
|
logger.info(`Function ${file} executed successfully`);
|
||||||
|
|||||||
@@ -26,15 +26,15 @@ console.log('Используемый порт:', process.env.PORT || 8000);
|
|||||||
async function initServices() {
|
async function initServices() {
|
||||||
try {
|
try {
|
||||||
console.log('Инициализация сервисов...');
|
console.log('Инициализация сервисов...');
|
||||||
|
|
||||||
// Останавливаем предыдущий экземпляр бота
|
// Останавливаем предыдущий экземпляр бота
|
||||||
await stopBot();
|
await stopBot();
|
||||||
|
|
||||||
// Добавляем обработку ошибок при запуске бота
|
// Добавляем обработку ошибок при запуске бота
|
||||||
try {
|
try {
|
||||||
await getBot(); // getBot теперь асинхронный и сам запускает бота
|
await getBot(); // getBot теперь асинхронный и сам запускает бота
|
||||||
console.log('Telegram bot started');
|
console.log('Telegram bot started');
|
||||||
|
|
||||||
// Добавляем graceful shutdown
|
// Добавляем graceful shutdown
|
||||||
process.once('SIGINT', async () => {
|
process.once('SIGINT', async () => {
|
||||||
await stopBot();
|
await stopBot();
|
||||||
@@ -46,14 +46,16 @@ async function initServices() {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 409) {
|
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 {
|
} else {
|
||||||
logger.error('Error launching Telegram bot:', error);
|
logger.error('Error launching Telegram bot:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Все сервисы успешно инициализированы');
|
console.log('Все сервисы успешно инициализированы');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка при инициализации сервисов:', error);
|
console.error('Ошибка при инициализации сервисов:', error);
|
||||||
@@ -61,20 +63,22 @@ async function initServices() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Настройка сессий
|
// Настройка сессий
|
||||||
app.use(session({
|
app.use(
|
||||||
store: new pgSession({
|
session({
|
||||||
pool: pool,
|
store: new pgSession({
|
||||||
tableName: 'session'
|
pool: pool,
|
||||||
}),
|
tableName: 'session',
|
||||||
secret: process.env.SESSION_SECRET || 'hb3atoken',
|
}),
|
||||||
resave: false,
|
secret: process.env.SESSION_SECRET || 'hb3atoken',
|
||||||
saveUninitialized: true,
|
resave: false,
|
||||||
cookie: {
|
saveUninitialized: true,
|
||||||
secure: process.env.NODE_ENV === 'production',
|
cookie: {
|
||||||
httpOnly: true,
|
secure: process.env.NODE_ENV === 'production',
|
||||||
maxAge: 30 * 24 * 60 * 60 * 1000 // 30 дней
|
httpOnly: true,
|
||||||
}
|
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 дней
|
||||||
}));
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// Маршруты API
|
// Маршруты API
|
||||||
app.use('/api/users', usersRouter);
|
app.use('/api/users', usersRouter);
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ class AIAssistant {
|
|||||||
|
|
||||||
// Создание экземпляра ChatOllama с нужными параметрами
|
// Создание экземпляра ChatOllama с нужными параметрами
|
||||||
createChat(language = 'ru') {
|
createChat(language = 'ru') {
|
||||||
const systemPrompt = language === 'ru'
|
const systemPrompt =
|
||||||
? 'Вы - полезный ассистент. Отвечайте на русском языке.'
|
language === 'ru'
|
||||||
: 'You are a helpful assistant. Respond in English.';
|
? 'Вы - полезный ассистент. Отвечайте на русском языке.'
|
||||||
|
: 'You are a helpful assistant. Respond in English.';
|
||||||
|
|
||||||
return new ChatOllama({
|
return new ChatOllama({
|
||||||
baseUrl: this.baseUrl,
|
baseUrl: this.baseUrl,
|
||||||
@@ -22,7 +23,7 @@ class AIAssistant {
|
|||||||
system: systemPrompt,
|
system: systemPrompt,
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
maxTokens: 1000,
|
maxTokens: 1000,
|
||||||
timeout: 30000 // 30 секунд таймаут
|
timeout: 30000, // 30 секунд таймаут
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,14 +37,12 @@ class AIAssistant {
|
|||||||
async getResponse(message, language = 'auto') {
|
async getResponse(message, language = 'auto') {
|
||||||
try {
|
try {
|
||||||
console.log('getResponse called with:', { message, language });
|
console.log('getResponse called with:', { message, language });
|
||||||
|
|
||||||
// Определяем язык, если не указан явно
|
// Определяем язык, если не указан явно
|
||||||
const detectedLanguage = language === 'auto'
|
const detectedLanguage = language === 'auto' ? this.detectLanguage(message) : language;
|
||||||
? this.detectLanguage(message)
|
|
||||||
: language;
|
|
||||||
|
|
||||||
console.log('Detected language:', detectedLanguage);
|
console.log('Detected language:', detectedLanguage);
|
||||||
|
|
||||||
// Сначала пробуем прямой API запрос
|
// Сначала пробуем прямой API запрос
|
||||||
try {
|
try {
|
||||||
console.log('Trying direct API request...');
|
console.log('Trying direct API request...');
|
||||||
@@ -67,7 +66,7 @@ class AIAssistant {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in getResponse:', error);
|
console.error('Error in getResponse:', error);
|
||||||
return "Извините, я не смог обработать ваш запрос. Пожалуйста, попробуйте позже.";
|
return 'Извините, я не смог обработать ваш запрос. Пожалуйста, попробуйте позже.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,10 +74,11 @@ class AIAssistant {
|
|||||||
async fallbackRequest(message, language) {
|
async fallbackRequest(message, language) {
|
||||||
try {
|
try {
|
||||||
console.log('Using fallback request method with:', { message, language });
|
console.log('Using fallback request method with:', { message, language });
|
||||||
|
|
||||||
const systemPrompt = language === 'ru'
|
const systemPrompt =
|
||||||
? 'Вы - полезный ассистент. Отвечайте на русском языке.'
|
language === 'ru'
|
||||||
: 'You are a helpful assistant. Respond in English.';
|
? 'Вы - полезный ассистент. Отвечайте на русском языке.'
|
||||||
|
: 'You are a helpful assistant. Respond in English.';
|
||||||
|
|
||||||
console.log('Sending request to Ollama API...');
|
console.log('Sending request to Ollama API...');
|
||||||
const response = await fetch(`${this.baseUrl}/api/generate`, {
|
const response = await fetch(`${this.baseUrl}/api/generate`, {
|
||||||
@@ -91,15 +91,15 @@ class AIAssistant {
|
|||||||
stream: false,
|
stream: false,
|
||||||
options: {
|
options: {
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
num_predict: 1000
|
num_predict: 1000,
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log('Ollama API response:', data);
|
console.log('Ollama API response:', data);
|
||||||
return data.response;
|
return data.response;
|
||||||
|
|||||||
@@ -6,15 +6,13 @@ const { processMessage } = require('./ai-assistant'); // Используем AI
|
|||||||
const verificationService = require('./verification-service'); // Используем сервис верификации
|
const verificationService = require('./verification-service'); // Используем сервис верификации
|
||||||
|
|
||||||
const ADMIN_CONTRACTS = [
|
const ADMIN_CONTRACTS = [
|
||||||
{ address: "0xd95a45fc46a7300e6022885afec3d618d7d3f27c", network: "eth" },
|
{ address: '0xd95a45fc46a7300e6022885afec3d618d7d3f27c', network: 'eth' },
|
||||||
{ address: "0x4B294265720B09ca39BFBA18c7E368413c0f68eB", network: "bsc" },
|
{ address: '0x4B294265720B09ca39BFBA18c7E368413c0f68eB', network: 'bsc' },
|
||||||
{ address: "0xdce769b847a0a697239777d0b1c7dd33b6012ba0", network: "arbitrum" },
|
{ address: '0xdce769b847a0a697239777d0b1c7dd33b6012ba0', network: 'arbitrum' },
|
||||||
{ address: "0x351f59de4fedbdf7601f5592b93db3b9330c1c1d", network: "polygon" }
|
{ address: '0x351f59de4fedbdf7601f5592b93db3b9330c1c1d', network: 'polygon' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const ERC20_ABI = [
|
const ERC20_ABI = ['function balanceOf(address owner) view returns (uint256)'];
|
||||||
"function balanceOf(address owner) view returns (uint256)"
|
|
||||||
];
|
|
||||||
|
|
||||||
class AuthService {
|
class AuthService {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -22,7 +20,7 @@ class AuthService {
|
|||||||
eth: new ethers.JsonRpcProvider(process.env.RPC_URL_ETH),
|
eth: new ethers.JsonRpcProvider(process.env.RPC_URL_ETH),
|
||||||
polygon: new ethers.JsonRpcProvider(process.env.RPC_URL_POLYGON),
|
polygon: new ethers.JsonRpcProvider(process.env.RPC_URL_POLYGON),
|
||||||
bsc: new ethers.JsonRpcProvider(process.env.RPC_URL_BSC),
|
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),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,13 +28,13 @@ class AuthService {
|
|||||||
async verifySignature(message, signature, address) {
|
async verifySignature(message, signature, address) {
|
||||||
try {
|
try {
|
||||||
if (!message || !signature || !address) return false;
|
if (!message || !signature || !address) return false;
|
||||||
|
|
||||||
// Нормализуем входящий адрес
|
// Нормализуем входящий адрес
|
||||||
const normalizedAddress = ethers.getAddress(address).toLowerCase();
|
const normalizedAddress = ethers.getAddress(address).toLowerCase();
|
||||||
|
|
||||||
// Восстанавливаем адрес из подписи
|
// Восстанавливаем адрес из подписи
|
||||||
const recoveredAddress = ethers.verifyMessage(message, signature);
|
const recoveredAddress = ethers.verifyMessage(message, signature);
|
||||||
|
|
||||||
// Сравниваем нормализованные адреса
|
// Сравниваем нормализованные адреса
|
||||||
return ethers.getAddress(recoveredAddress).toLowerCase() === normalizedAddress;
|
return ethers.getAddress(recoveredAddress).toLowerCase() === normalizedAddress;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -54,20 +52,23 @@ class AuthService {
|
|||||||
try {
|
try {
|
||||||
// Нормализуем адрес - всегда приводим к нижнему регистру
|
// Нормализуем адрес - всегда приводим к нижнему регистру
|
||||||
const normalizedAddress = ethers.getAddress(address).toLowerCase();
|
const normalizedAddress = ethers.getAddress(address).toLowerCase();
|
||||||
|
|
||||||
// Ищем пользователя по адресу в таблице user_identities
|
// Ищем пользователя по адресу в таблице user_identities
|
||||||
const userResult = await db.query(`
|
const userResult = await db.query(
|
||||||
|
`
|
||||||
SELECT u.* FROM users u
|
SELECT u.* FROM users u
|
||||||
JOIN user_identities ui ON u.id = ui.user_id
|
JOIN user_identities ui ON u.id = ui.user_id
|
||||||
WHERE ui.provider = 'wallet' AND ui.provider_id = $1
|
WHERE ui.provider = 'wallet' AND ui.provider_id = $1
|
||||||
`, [normalizedAddress]);
|
`,
|
||||||
|
[normalizedAddress]
|
||||||
|
);
|
||||||
|
|
||||||
if (userResult.rows.length > 0) {
|
if (userResult.rows.length > 0) {
|
||||||
const user = userResult.rows[0];
|
const user = userResult.rows[0];
|
||||||
|
|
||||||
// Проверяем роль администратора при каждой аутентификации
|
// Проверяем роль администратора при каждой аутентификации
|
||||||
const isAdmin = await this.checkAdminRole(normalizedAddress);
|
const isAdmin = await this.checkAdminRole(normalizedAddress);
|
||||||
|
|
||||||
// Если статус админа изменился, обновляем роль в базе данных
|
// Если статус админа изменился, обновляем роль в базе данных
|
||||||
if (user.role === 'admin' && !isAdmin) {
|
if (user.role === 'admin' && !isAdmin) {
|
||||||
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['user', user.id]);
|
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['user', user.id]);
|
||||||
@@ -78,37 +79,38 @@ class AuthService {
|
|||||||
logger.info(`Updated user ${user.id} role to admin (admin tokens found)`);
|
logger.info(`Updated user ${user.id} role to admin (admin tokens found)`);
|
||||||
return { userId: user.id, isAdmin: true };
|
return { userId: user.id, isAdmin: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
isAdmin: user.role === 'admin'
|
isAdmin: user.role === 'admin',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если пользователь не найден, создаем нового
|
// Если пользователь не найден, создаем нового
|
||||||
const newUserResult = await db.query(
|
const newUserResult = await db.query('INSERT INTO users (role) VALUES ($1) RETURNING id', [
|
||||||
'INSERT INTO users (role) VALUES ($1) RETURNING id',
|
'user',
|
||||||
['user']
|
]);
|
||||||
);
|
|
||||||
|
|
||||||
const userId = newUserResult.rows[0].id;
|
const userId = newUserResult.rows[0].id;
|
||||||
|
|
||||||
// Добавляем идентификатор кошелька (всегда в нижнем регистре)
|
// Добавляем идентификатор кошелька (всегда в нижнем регистре)
|
||||||
await db.query(
|
await db.query(
|
||||||
'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)',
|
'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)',
|
||||||
[userId, 'wallet', normalizedAddress]
|
[userId, 'wallet', normalizedAddress]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Проверяем, есть ли у пользователя роль админа
|
// Проверяем, есть ли у пользователя роль админа
|
||||||
const isAdmin = await this.checkAdminRole(normalizedAddress);
|
const isAdmin = await this.checkAdminRole(normalizedAddress);
|
||||||
logger.info(`New user ${userId} role check result: ${isAdmin ? 'admin' : 'user'}`);
|
logger.info(`New user ${userId} role check result: ${isAdmin ? 'admin' : 'user'}`);
|
||||||
|
|
||||||
// Если у пользователя есть админские токены, обновляем его роль
|
// Если у пользователя есть админские токены, обновляем его роль
|
||||||
if (isAdmin) {
|
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(`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 };
|
return { userId, isAdmin };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error finding or creating user:', error);
|
logger.error('Error finding or creating user:', error);
|
||||||
@@ -123,13 +125,13 @@ class AuthService {
|
|||||||
*/
|
*/
|
||||||
async checkAdminRole(address) {
|
async checkAdminRole(address) {
|
||||||
if (!address) return false;
|
if (!address) return false;
|
||||||
|
|
||||||
logger.info(`Checking admin role for address: ${address}`);
|
logger.info(`Checking admin role for address: ${address}`);
|
||||||
let foundTokens = false;
|
let foundTokens = false;
|
||||||
let errorCount = 0;
|
let errorCount = 0;
|
||||||
const balances = {};
|
const balances = {};
|
||||||
const totalNetworks = ADMIN_CONTRACTS.length;
|
const totalNetworks = ADMIN_CONTRACTS.length;
|
||||||
|
|
||||||
// Создаем массив промисов для параллельной проверки балансов
|
// Создаем массив промисов для параллельной проверки балансов
|
||||||
const checkPromises = ADMIN_CONTRACTS.map(async (contract) => {
|
const checkPromises = ADMIN_CONTRACTS.map(async (contract) => {
|
||||||
try {
|
try {
|
||||||
@@ -140,83 +142,83 @@ class AuthService {
|
|||||||
errorCount++;
|
errorCount++;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем доступность провайдера
|
// Проверяем доступность провайдера
|
||||||
try {
|
try {
|
||||||
// Проверка доступности сети с таймаутом
|
// Проверка доступности сети с таймаутом
|
||||||
const networkCheckPromise = provider.getNetwork();
|
const networkCheckPromise = provider.getNetwork();
|
||||||
const timeoutPromise = new Promise((_, reject) =>
|
const timeoutPromise = new Promise((_, reject) =>
|
||||||
setTimeout(() => reject(new Error('Network check timeout')), 3000)
|
setTimeout(() => reject(new Error('Network check timeout')), 3000)
|
||||||
);
|
);
|
||||||
|
|
||||||
await Promise.race([networkCheckPromise, timeoutPromise]);
|
await Promise.race([networkCheckPromise, timeoutPromise]);
|
||||||
} catch (networkError) {
|
} 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';
|
balances[contract.network] = 'Error: Network unavailable';
|
||||||
errorCount++;
|
errorCount++;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokenContract = new ethers.Contract(
|
const tokenContract = new ethers.Contract(contract.address, ERC20_ABI, provider);
|
||||||
contract.address,
|
|
||||||
ERC20_ABI,
|
|
||||||
provider
|
|
||||||
);
|
|
||||||
|
|
||||||
// Создаем промис с таймаутом
|
// Создаем промис с таймаутом
|
||||||
const balancePromise = tokenContract.balanceOf(address);
|
const balancePromise = tokenContract.balanceOf(address);
|
||||||
const timeoutPromise = new Promise((_, reject) =>
|
const timeoutPromise = new Promise((_, reject) =>
|
||||||
setTimeout(() => reject(new Error('Timeout')), 3000)
|
setTimeout(() => reject(new Error('Timeout')), 3000)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ждем первый выполненный промис
|
// Ждем первый выполненный промис
|
||||||
const balance = await Promise.race([balancePromise, timeoutPromise]);
|
const balance = await Promise.race([balancePromise, timeoutPromise]);
|
||||||
const formattedBalance = ethers.formatUnits(balance, 18);
|
const formattedBalance = ethers.formatUnits(balance, 18);
|
||||||
balances[contract.network] = formattedBalance;
|
balances[contract.network] = formattedBalance;
|
||||||
|
|
||||||
logger.info(`Token balance on ${contract.network}:`, {
|
logger.info(`Token balance on ${contract.network}:`, {
|
||||||
address,
|
address,
|
||||||
contract: contract.address,
|
contract: contract.address,
|
||||||
balance: formattedBalance,
|
balance: formattedBalance,
|
||||||
hasTokens: balance > 0
|
hasTokens: balance > 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (parseFloat(formattedBalance) > 0) {
|
if (parseFloat(formattedBalance) > 0) {
|
||||||
logger.info(`Found admin tokens on ${contract.network}`);
|
logger.info(`Found admin tokens on ${contract.network}`);
|
||||||
foundTokens = true;
|
foundTokens = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { network: contract.network, balance: formattedBalance };
|
return { network: contract.network, balance: formattedBalance };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Error checking balance in ${contract.network}:`, {
|
logger.error(`Error checking balance in ${contract.network}:`, {
|
||||||
address,
|
address,
|
||||||
contract: contract.address,
|
contract: contract.address,
|
||||||
error: error.message || 'Unknown error'
|
error: error.message || 'Unknown error',
|
||||||
});
|
});
|
||||||
balances[contract.network] = 'Error';
|
balances[contract.network] = 'Error';
|
||||||
errorCount++;
|
errorCount++;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ждем выполнения всех проверок
|
// Ждем выполнения всех проверок
|
||||||
await Promise.all(checkPromises);
|
await Promise.all(checkPromises);
|
||||||
|
|
||||||
// Если все запросы завершились с ошибкой, считаем, что проверка не удалась
|
// Если все запросы завершились с ошибкой, считаем, что проверка не удалась
|
||||||
if (errorCount === totalNetworks) {
|
if (errorCount === totalNetworks) {
|
||||||
logger.error(`All network checks for ${address} failed. Cannot verify admin status.`);
|
logger.error(`All network checks for ${address} failed. Cannot verify admin status.`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foundTokens) {
|
if (foundTokens) {
|
||||||
logger.info(`Admin role summary for ${address}:`, {
|
logger.info(`Admin role summary for ${address}:`, {
|
||||||
networks: Object.keys(balances).filter(net => balances[net] > 0 && balances[net] !== 'Error'),
|
networks: Object.keys(balances).filter(
|
||||||
balances
|
(net) => balances[net] > 0 && balances[net] !== 'Error'
|
||||||
|
),
|
||||||
|
balances,
|
||||||
});
|
});
|
||||||
logger.info(`Admin role granted for ${address}`);
|
logger.info(`Admin role granted for ${address}`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Admin role denied - no tokens found for ${address}`);
|
logger.info(`Admin role denied - no tokens found for ${address}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -233,13 +235,13 @@ class AuthService {
|
|||||||
eth: '0',
|
eth: '0',
|
||||||
bsc: '0',
|
bsc: '0',
|
||||||
arbitrum: '0',
|
arbitrum: '0',
|
||||||
polygon: '0'
|
polygon: '0',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const balances = {};
|
const balances = {};
|
||||||
const timeout = 3000; // 3 секунды таймаут
|
const timeout = 3000; // 3 секунды таймаут
|
||||||
|
|
||||||
for (const contract of ADMIN_CONTRACTS) {
|
for (const contract of ADMIN_CONTRACTS) {
|
||||||
try {
|
try {
|
||||||
const provider = this.providers[contract.network];
|
const provider = this.providers[contract.network];
|
||||||
@@ -253,37 +255,35 @@ class AuthService {
|
|||||||
try {
|
try {
|
||||||
// Проверка доступности сети с таймаутом
|
// Проверка доступности сети с таймаутом
|
||||||
const networkCheckPromise = provider.getNetwork();
|
const networkCheckPromise = provider.getNetwork();
|
||||||
const networkTimeoutPromise = new Promise((_, reject) =>
|
const networkTimeoutPromise = new Promise((_, reject) =>
|
||||||
setTimeout(() => reject(new Error('Network check timeout')), timeout)
|
setTimeout(() => reject(new Error('Network check timeout')), timeout)
|
||||||
);
|
);
|
||||||
|
|
||||||
await Promise.race([networkCheckPromise, networkTimeoutPromise]);
|
await Promise.race([networkCheckPromise, networkTimeoutPromise]);
|
||||||
} catch (networkError) {
|
} 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';
|
balances[contract.network] = '0';
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokenContract = new ethers.Contract(
|
const tokenContract = new ethers.Contract(contract.address, ERC20_ABI, provider);
|
||||||
contract.address,
|
|
||||||
ERC20_ABI,
|
|
||||||
provider
|
|
||||||
);
|
|
||||||
|
|
||||||
// Создаем промис с таймаутом
|
// Создаем промис с таймаутом
|
||||||
const balancePromise = tokenContract.balanceOf(address);
|
const balancePromise = tokenContract.balanceOf(address);
|
||||||
const timeoutPromise = new Promise((_, reject) =>
|
const timeoutPromise = new Promise((_, reject) =>
|
||||||
setTimeout(() => reject(new Error('Timeout')), timeout)
|
setTimeout(() => reject(new Error('Timeout')), timeout)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ждем первый выполненный промис
|
// Ждем первый выполненный промис
|
||||||
const balance = await Promise.race([balancePromise, timeoutPromise]);
|
const balance = await Promise.race([balancePromise, timeoutPromise]);
|
||||||
const formattedBalance = ethers.formatUnits(balance, 18);
|
const formattedBalance = ethers.formatUnits(balance, 18);
|
||||||
|
|
||||||
logger.info(`Token balance for ${address} on ${contract.network}:`, {
|
logger.info(`Token balance for ${address} on ${contract.network}:`, {
|
||||||
contract: contract.address,
|
contract: contract.address,
|
||||||
balance: formattedBalance,
|
balance: formattedBalance,
|
||||||
timestamp: new Date().toISOString()
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
balances[contract.network] = formattedBalance;
|
balances[contract.network] = formattedBalance;
|
||||||
@@ -292,17 +292,17 @@ class AuthService {
|
|||||||
address,
|
address,
|
||||||
contract: contract.address,
|
contract: contract.address,
|
||||||
error: error.message || 'Unknown error',
|
error: error.message || 'Unknown error',
|
||||||
timestamp: new Date().toISOString()
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
balances[contract.network] = '0';
|
balances[contract.network] = '0';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Token balances fetched for ${address}:`, {
|
logger.info(`Token balances fetched for ${address}:`, {
|
||||||
...balances,
|
...balances,
|
||||||
timestamp: new Date().toISOString()
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
return balances;
|
return balances;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,26 +318,29 @@ class AuthService {
|
|||||||
session.userId = userId;
|
session.userId = userId;
|
||||||
session.authenticated = authenticated;
|
session.authenticated = authenticated;
|
||||||
session.authType = authType;
|
session.authType = authType;
|
||||||
|
|
||||||
// Сохраняем адрес кошелька если есть
|
// Сохраняем адрес кошелька если есть
|
||||||
if (address) {
|
if (address) {
|
||||||
session.address = address;
|
session.address = address;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем сессию в БД
|
// Сохраняем сессию в БД
|
||||||
const result = await db.query(
|
const result = await db.query(
|
||||||
`UPDATE session
|
`UPDATE session
|
||||||
SET sess = $1
|
SET sess = $1
|
||||||
WHERE sid = $2`,
|
WHERE sid = $2`,
|
||||||
[JSON.stringify({
|
[
|
||||||
userId,
|
JSON.stringify({
|
||||||
authenticated,
|
userId,
|
||||||
authType,
|
authenticated,
|
||||||
address,
|
authType,
|
||||||
cookie: session.cookie
|
address,
|
||||||
}), session.id]
|
cookie: session.cookie,
|
||||||
|
}),
|
||||||
|
session.id,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error creating session:', error);
|
logger.error('Error creating session:', error);
|
||||||
@@ -400,17 +403,19 @@ class AuthService {
|
|||||||
try {
|
try {
|
||||||
// Проверяем наличие связанного кошелька
|
// Проверяем наличие связанного кошелька
|
||||||
const wallet = await this.getLinkedWallet(userId);
|
const wallet = await this.getLinkedWallet(userId);
|
||||||
|
|
||||||
// Если кошелек не привязан, пользователь получает роль user
|
// Если кошелек не привязан, пользователь получает роль user
|
||||||
// с базовым доступом к чату и истории сообщений
|
// с базовым доступом к чату и истории сообщений
|
||||||
if (!wallet) {
|
if (!wallet) {
|
||||||
logger.info(`No wallet linked for user ${userId}, assigning basic user role`);
|
logger.info(`No wallet linked for user ${userId}, assigning basic user role`);
|
||||||
return 'user';
|
return 'user';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если есть кошелек, проверяем админские токены
|
// Если есть кошелек, проверяем админские токены
|
||||||
const isAdmin = await this.checkAdminRole(wallet);
|
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';
|
return isAdmin ? 'admin' : 'user';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error checking user role:', error);
|
logger.error('Error checking user role:', error);
|
||||||
@@ -423,20 +428,17 @@ class AuthService {
|
|||||||
try {
|
try {
|
||||||
// Проверяем код через сервис верификации
|
// Проверяем код через сервис верификации
|
||||||
const result = await verificationService.verifyCode(code, 'email', null);
|
const result = await verificationService.verifyCode(code, 'email', null);
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
return { verified: false };
|
return { verified: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = result.userId;
|
const userId = result.userId;
|
||||||
const email = result.providerId;
|
const email = result.providerId;
|
||||||
|
|
||||||
// Проверяем, существует ли пользователь с таким email
|
// Проверяем, существует ли пользователь с таким email
|
||||||
const userResult = await db.query(
|
const userResult = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
|
||||||
'SELECT * FROM users WHERE id = $1',
|
|
||||||
[userId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (userResult.rows.length === 0) {
|
if (userResult.rows.length === 0) {
|
||||||
return { verified: false };
|
return { verified: false };
|
||||||
}
|
}
|
||||||
@@ -444,7 +446,7 @@ class AuthService {
|
|||||||
// Проверяем наличие кошелька и определяем роль
|
// Проверяем наличие кошелька и определяем роль
|
||||||
const wallet = await this.getLinkedWallet(userId);
|
const wallet = await this.getLinkedWallet(userId);
|
||||||
let role = 'user'; // Базовая роль для доступа к чату
|
let role = 'user'; // Базовая роль для доступа к чату
|
||||||
|
|
||||||
if (wallet) {
|
if (wallet) {
|
||||||
// Если есть кошелек, проверяем баланс токенов
|
// Если есть кошелек, проверяем баланс токенов
|
||||||
const isAdmin = await this.checkAdminRole(wallet);
|
const isAdmin = await this.checkAdminRole(wallet);
|
||||||
@@ -453,13 +455,13 @@ class AuthService {
|
|||||||
} else {
|
} else {
|
||||||
logger.info(`User ${userId} has no wallet, using basic user role`);
|
logger.info(`User ${userId} has no wallet, using basic user role`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
verified: true,
|
verified: true,
|
||||||
userId,
|
userId,
|
||||||
email,
|
email,
|
||||||
role,
|
role,
|
||||||
wallet: wallet || null
|
wallet: wallet || null,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error checking email verification:', error);
|
logger.error('Error checking email verification:', error);
|
||||||
@@ -473,28 +475,30 @@ class AuthService {
|
|||||||
async verifyTelegramAuth(telegramId, verificationCode, session) {
|
async verifyTelegramAuth(telegramId, verificationCode, session) {
|
||||||
try {
|
try {
|
||||||
logger.info(`[verifyTelegramAuth] Starting for telegramId: ${telegramId}`);
|
logger.info(`[verifyTelegramAuth] Starting for telegramId: ${telegramId}`);
|
||||||
|
|
||||||
let userId;
|
let userId;
|
||||||
let isNewUser = false;
|
let isNewUser = false;
|
||||||
|
|
||||||
// Проверяем наличие аутентифицированного пользователя в сессии
|
// Проверяем наличие аутентифицированного пользователя в сессии
|
||||||
if (session && session.authenticated && session.userId) {
|
if (session && session.authenticated && session.userId) {
|
||||||
// Если есть авторизованный пользователь в сессии, связываем Telegram с ним
|
// Если есть авторизованный пользователь в сессии, связываем Telegram с ним
|
||||||
userId = session.userId;
|
userId = session.userId;
|
||||||
logger.info(`[verifyTelegramAuth] Using existing authenticated user ${userId} from session`);
|
logger.info(
|
||||||
|
`[verifyTelegramAuth] Using existing authenticated user ${userId} from session`
|
||||||
|
);
|
||||||
|
|
||||||
// Связываем Telegram с текущим пользователем
|
// Связываем Telegram с текущим пользователем
|
||||||
await this.linkIdentity(userId, 'telegram', telegramId);
|
await this.linkIdentity(userId, 'telegram', telegramId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
userId,
|
userId,
|
||||||
role: session.isAdmin ? 'admin' : 'user',
|
role: session.isAdmin ? 'admin' : 'user',
|
||||||
telegramId,
|
telegramId,
|
||||||
isNewUser: false
|
isNewUser: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если в сессии нет авторизованного пользователя, проверяем существующие идентификаторы
|
// Если в сессии нет авторизованного пользователя, проверяем существующие идентификаторы
|
||||||
// Проверяем, существует ли уже пользователь с таким Telegram ID
|
// Проверяем, существует ли уже пользователь с таким Telegram ID
|
||||||
const existingUserResult = await db.query(
|
const existingUserResult = await db.query(
|
||||||
@@ -509,23 +513,26 @@ class AuthService {
|
|||||||
if (existingUserResult.rows.length > 0) {
|
if (existingUserResult.rows.length > 0) {
|
||||||
const existingUser = existingUserResult.rows[0];
|
const existingUser = existingUserResult.rows[0];
|
||||||
userId = existingUser.id;
|
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 {
|
} else {
|
||||||
// Создаем нового пользователя для нового telegramId
|
// Создаем нового пользователя для нового telegramId
|
||||||
const newUserResult = await db.query(
|
const newUserResult = await db.query('INSERT INTO users (role) VALUES ($1) RETURNING id', [
|
||||||
'INSERT INTO users (role) VALUES ($1) RETURNING id',
|
'user',
|
||||||
['user']
|
]);
|
||||||
);
|
|
||||||
userId = newUserResult.rows[0].id;
|
userId = newUserResult.rows[0].id;
|
||||||
isNewUser = true;
|
isNewUser = true;
|
||||||
|
|
||||||
// Добавляем Telegram идентификатор
|
// Добавляем Telegram идентификатор
|
||||||
await db.query(
|
await db.query(
|
||||||
'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)',
|
'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)',
|
||||||
[userId, 'telegram', telegramId]
|
[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 в сессии, сохраняем его для нового пользователя
|
// Если есть гостевой ID в сессии, сохраняем его для нового пользователя
|
||||||
@@ -542,7 +549,7 @@ class AuthService {
|
|||||||
userId,
|
userId,
|
||||||
role: 'user',
|
role: 'user',
|
||||||
telegramId,
|
telegramId,
|
||||||
isNewUser
|
isNewUser,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[verifyTelegramAuth] Error:', error);
|
logger.error('[verifyTelegramAuth] Error:', error);
|
||||||
@@ -553,30 +560,28 @@ class AuthService {
|
|||||||
// Добавляем псевдоним функции checkAdminRole для обратной совместимости
|
// Добавляем псевдоним функции checkAdminRole для обратной совместимости
|
||||||
async checkAdminTokens(address) {
|
async checkAdminTokens(address) {
|
||||||
if (!address) return false;
|
if (!address) return false;
|
||||||
|
|
||||||
logger.info(`Checking admin tokens for address: ${address}`);
|
logger.info(`Checking admin tokens for address: ${address}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const isAdmin = await this.checkAdminRole(address);
|
const isAdmin = await this.checkAdminRole(address);
|
||||||
|
|
||||||
// Обновляем роль пользователя в базе данных, если есть админские токены
|
// Обновляем роль пользователя в базе данных, если есть админские токены
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
try {
|
try {
|
||||||
// Находим userId по адресу
|
// Находим userId по адресу
|
||||||
const userResult = await db.query(`
|
const userResult = await db.query(
|
||||||
|
`
|
||||||
SELECT u.id FROM users u
|
SELECT u.id FROM users u
|
||||||
JOIN user_identities ui ON u.id = ui.user_id
|
JOIN user_identities ui ON u.id = ui.user_id
|
||||||
WHERE ui.provider = 'wallet' AND ui.provider_id = $1`,
|
WHERE ui.provider = 'wallet' AND ui.provider_id = $1`,
|
||||||
[address.toLowerCase()]
|
[address.toLowerCase()]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (userResult.rows.length > 0) {
|
if (userResult.rows.length > 0) {
|
||||||
const userId = userResult.rows[0].id;
|
const userId = userResult.rows[0].id;
|
||||||
// Обновляем роль пользователя
|
// Обновляем роль пользователя
|
||||||
await db.query(
|
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
|
||||||
'UPDATE users SET role = $1 WHERE id = $2',
|
|
||||||
['admin', userId]
|
|
||||||
);
|
|
||||||
logger.info(`Updated user ${userId} role to admin based on token holdings`);
|
logger.info(`Updated user ${userId} role to admin based on token holdings`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -586,26 +591,24 @@ class AuthService {
|
|||||||
} else {
|
} else {
|
||||||
// Если пользователь не является администратором, сбрасываем роль на "user", если она была "admin"
|
// Если пользователь не является администратором, сбрасываем роль на "user", если она была "admin"
|
||||||
try {
|
try {
|
||||||
const userResult = await db.query(`
|
const userResult = await db.query(
|
||||||
|
`
|
||||||
SELECT u.id, u.role FROM users u
|
SELECT u.id, u.role FROM users u
|
||||||
JOIN user_identities ui ON u.id = ui.user_id
|
JOIN user_identities ui ON u.id = ui.user_id
|
||||||
WHERE ui.provider = 'wallet' AND ui.provider_id = $1`,
|
WHERE ui.provider = 'wallet' AND ui.provider_id = $1`,
|
||||||
[address.toLowerCase()]
|
[address.toLowerCase()]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (userResult.rows.length > 0 && userResult.rows[0].role === 'admin') {
|
if (userResult.rows.length > 0 && userResult.rows[0].role === 'admin') {
|
||||||
const userId = userResult.rows[0].id;
|
const userId = userResult.rows[0].id;
|
||||||
await db.query(
|
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['user', userId]);
|
||||||
'UPDATE users SET role = $1 WHERE id = $2',
|
|
||||||
['user', userId]
|
|
||||||
);
|
|
||||||
logger.info(`Reset user ${userId} role from admin to user (no tokens found)`);
|
logger.info(`Reset user ${userId} role from admin to user (no tokens found)`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error updating user role:', error);
|
logger.error('Error updating user role:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return isAdmin;
|
return isAdmin;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Error in checkAdminTokens: ${error.message}`);
|
logger.error(`Error in checkAdminTokens: ${error.message}`);
|
||||||
@@ -622,24 +625,21 @@ class AuthService {
|
|||||||
try {
|
try {
|
||||||
// Получаем все идентификаторы пользователя
|
// Получаем все идентификаторы пользователя
|
||||||
const identities = await this.getUserIdentities(userId);
|
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, удаляем старые
|
// Если гостевых идентификаторов больше 3, удаляем старые
|
||||||
if (guestIdentities.length > 3) {
|
if (guestIdentities.length > 3) {
|
||||||
// Сортируем по дате создания (новые первые)
|
// Сортируем по дате создания (новые первые)
|
||||||
guestIdentities.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
guestIdentities.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
||||||
|
|
||||||
// Оставляем только 3 последних идентификатора
|
// Оставляем только 3 последних идентификатора
|
||||||
const identitiesToDelete = guestIdentities.slice(3);
|
const identitiesToDelete = guestIdentities.slice(3);
|
||||||
|
|
||||||
// Удаляем старые идентификаторы
|
// Удаляем старые идентификаторы
|
||||||
for (const identity of identitiesToDelete) {
|
for (const identity of identitiesToDelete) {
|
||||||
await db.query(
|
await db.query('DELETE FROM user_identities WHERE id = $1', [identity.id]);
|
||||||
'DELETE FROM user_identities WHERE id = $1',
|
|
||||||
[identity.id]
|
|
||||||
);
|
|
||||||
logger.info(`Deleted old guest identity: ${identity.identity_value}`);
|
logger.info(`Deleted old guest identity: ${identity.identity_value}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -676,9 +676,7 @@ class AuthService {
|
|||||||
try {
|
try {
|
||||||
const balance = await Promise.race([
|
const balance = await Promise.race([
|
||||||
this.getTokenBalance(address, ADMIN_CONTRACTS.ARBITRUM),
|
this.getTokenBalance(address, ADMIN_CONTRACTS.ARBITRUM),
|
||||||
new Promise((_, reject) =>
|
new Promise((_, reject) => setTimeout(() => reject(new Error('TIMEOUT')), timeout)),
|
||||||
setTimeout(() => reject(new Error('TIMEOUT')), timeout)
|
|
||||||
)
|
|
||||||
]);
|
]);
|
||||||
return { balance, hasTokens: balance > 0 };
|
return { balance, hasTokens: balance > 0 };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -697,10 +695,12 @@ class AuthService {
|
|||||||
async linkIdentity(userId, provider, providerId) {
|
async linkIdentity(userId, provider, providerId) {
|
||||||
try {
|
try {
|
||||||
if (!userId || !provider || !providerId) {
|
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');
|
throw new Error('Missing parameters');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Нормализуем значение идентификатора
|
// Нормализуем значение идентификатора
|
||||||
let normalizedProviderId = providerId;
|
let normalizedProviderId = providerId;
|
||||||
if (provider === 'wallet') {
|
if (provider === 'wallet') {
|
||||||
@@ -714,113 +714,63 @@ class AuthService {
|
|||||||
} else if (provider === 'email') {
|
} else if (provider === 'email') {
|
||||||
normalizedProviderId = providerId.toLowerCase();
|
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(
|
const existingResult = await db.query(
|
||||||
`SELECT user_id FROM user_identities WHERE provider = $1 AND provider_id = $2`,
|
`SELECT user_id FROM user_identities WHERE provider = $1 AND provider_id = $2`,
|
||||||
[provider, normalizedProviderId]
|
[provider, normalizedProviderId]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (existingResult.rows.length > 0) {
|
if (existingResult.rows.length > 0) {
|
||||||
const existingUserId = existingResult.rows[0].user_id;
|
const existingUserId = existingResult.rows[0].user_id;
|
||||||
|
|
||||||
// Если идентификатор уже принадлежит этому пользователю, ничего не делаем
|
// Если идентификатор уже принадлежит этому пользователю, ничего не делаем
|
||||||
if (existingUserId === userId) {
|
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' };
|
return { success: true, message: 'Identity already exists' };
|
||||||
} else {
|
} 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})`);
|
throw new Error(`Identity already belongs to another user (${existingUserId})`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Добавляем новый идентификатор для пользователя
|
// Добавляем новый идентификатор для пользователя
|
||||||
await db.query(
|
await db.query(
|
||||||
`INSERT INTO user_identities (user_id, provider, provider_id)
|
`INSERT INTO user_identities (user_id, provider, provider_id)
|
||||||
VALUES ($1, $2, $3)`,
|
VALUES ($1, $2, $3)`,
|
||||||
[userId, provider, normalizedProviderId]
|
[userId, provider, normalizedProviderId]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Проверяем и обновляем роль администратора, если это идентификатор кошелька
|
// Проверяем и обновляем роль администратора, если это идентификатор кошелька
|
||||||
let isAdmin = false;
|
let isAdmin = false;
|
||||||
if (provider === 'wallet') {
|
if (provider === 'wallet') {
|
||||||
isAdmin = await this.checkAdminTokens(normalizedProviderId);
|
isAdmin = await this.checkAdminTokens(normalizedProviderId);
|
||||||
|
|
||||||
// Обновляем роль пользователя в базе данных, если нужно
|
// Обновляем роль пользователя в базе данных, если нужно
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
await db.query(
|
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
|
||||||
'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] 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 };
|
return { success: true, isAdmin };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`[AuthService] Error linking identity ${provider}:${providerId} to user ${userId}:`, error);
|
logger.error(
|
||||||
throw error;
|
`[AuthService] Error linking identity ${provider}:${providerId} to user ${userId}:`,
|
||||||
}
|
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]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
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;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -828,4 +778,4 @@ class AuthService {
|
|||||||
|
|
||||||
// Создаем и экспортируем единственный экземпляр
|
// Создаем и экспортируем единственный экземпляр
|
||||||
const authService = new AuthService();
|
const authService = new AuthService();
|
||||||
module.exports = authService;
|
module.exports = authService;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class EmailAuth {
|
|||||||
if (!email || !email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
|
if (!email || !email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
|
||||||
throw new Error('Некорректный формат email');
|
throw new Error('Некорректный формат email');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, существует ли пользователь с таким email
|
// Проверяем, существует ли пользователь с таким email
|
||||||
const existingEmailUser = await db.query(
|
const existingEmailUser = await db.query(
|
||||||
`SELECT u.id FROM users u
|
`SELECT u.id FROM users u
|
||||||
@@ -23,44 +23,47 @@ class EmailAuth {
|
|||||||
WHERE i.provider = 'email' AND i.provider_id = $1`,
|
WHERE i.provider = 'email' AND i.provider_id = $1`,
|
||||||
[email.toLowerCase()]
|
[email.toLowerCase()]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Создаем или получаем ID пользователя
|
// Создаем или получаем ID пользователя
|
||||||
let userId;
|
let userId;
|
||||||
|
|
||||||
if (session.authenticated && session.userId) {
|
if (session.authenticated && session.userId) {
|
||||||
// Если пользователь уже аутентифицирован, используем его ID
|
// Если пользователь уже аутентифицирован, используем его ID
|
||||||
userId = session.userId;
|
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) {
|
} else if (existingEmailUser.rows.length > 0) {
|
||||||
// Если найден пользователь с таким email, используем его ID
|
// Если найден пользователь с таким email, используем его ID
|
||||||
userId = existingEmailUser.rows[0].id;
|
userId = existingEmailUser.rows[0].id;
|
||||||
logger.info(`[initEmailAuth] Found existing user ${userId} with email ${email}`);
|
logger.info(`[initEmailAuth] Found existing user ${userId} with email ${email}`);
|
||||||
} else {
|
} else {
|
||||||
// Создаем временного пользователя, если нужно будет создать нового
|
// Создаем временного пользователя, если нужно будет создать нового
|
||||||
const userResult = await db.query(
|
const userResult = await db.query('INSERT INTO users (role) VALUES ($1) RETURNING id', [
|
||||||
'INSERT INTO users (role) VALUES ($1) RETURNING id',
|
'user',
|
||||||
['user']
|
]);
|
||||||
);
|
|
||||||
userId = userResult.rows[0].id;
|
userId = userResult.rows[0].id;
|
||||||
session.tempUserId = userId;
|
session.tempUserId = userId;
|
||||||
logger.info(`[initEmailAuth] Created temporary user ${userId} for email ${email}`);
|
logger.info(`[initEmailAuth] Created temporary user ${userId} for email ${email}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем email в сессии
|
// Сохраняем email в сессии
|
||||||
session.pendingEmail = email.toLowerCase();
|
session.pendingEmail = email.toLowerCase();
|
||||||
|
|
||||||
// Создаем код через сервис верификации
|
// Создаем код через сервис верификации
|
||||||
const verificationCode = await verificationService.createVerificationCode(
|
const verificationCode = await verificationService.createVerificationCode(
|
||||||
'email',
|
'email',
|
||||||
email.toLowerCase(),
|
email.toLowerCase(),
|
||||||
userId
|
userId
|
||||||
);
|
);
|
||||||
|
|
||||||
// Отправляем код на email
|
// Отправляем код на email
|
||||||
await this.emailBot.sendVerificationCode(email, verificationCode);
|
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 };
|
return { success: true, verificationCode };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error in email auth initialization:', error);
|
logger.error('Error in email auth initialization:', error);
|
||||||
@@ -80,7 +83,7 @@ class EmailAuth {
|
|||||||
|
|
||||||
// Проверяем код через сервис верификации
|
// Проверяем код через сервис верификации
|
||||||
const result = await verificationService.verifyCode(code, 'email', session.pendingEmail);
|
const result = await verificationService.verifyCode(code, 'email', session.pendingEmail);
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
// Используем сообщение об ошибке из сервиса верификации
|
// Используем сообщение об ошибке из сервиса верификации
|
||||||
return { verified: false, message: result.error || 'Неверный код верификации' };
|
return { verified: false, message: result.error || 'Неверный код верификации' };
|
||||||
@@ -93,28 +96,31 @@ class EmailAuth {
|
|||||||
if (session.authenticated && session.userId) {
|
if (session.authenticated && session.userId) {
|
||||||
finalUserId = session.userId;
|
finalUserId = session.userId;
|
||||||
logger.info(`[checkEmailVerification] Using existing authenticated user ${finalUserId}`);
|
logger.info(`[checkEmailVerification] Using existing authenticated user ${finalUserId}`);
|
||||||
|
|
||||||
// Связываем email с существующим пользователем
|
// Связываем email с существующим пользователем
|
||||||
await authService.linkIdentity(finalUserId, 'email', email);
|
await authService.linkIdentity(finalUserId, 'email', email);
|
||||||
|
|
||||||
// Очищаем временные данные
|
// Очищаем временные данные
|
||||||
delete session.pendingEmail;
|
delete session.pendingEmail;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
verified: true,
|
verified: true,
|
||||||
userId: finalUserId,
|
userId: finalUserId,
|
||||||
email: email
|
email: email,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если пользователь не авторизован, ищем всех пользователей с похожими идентификаторами
|
// Если пользователь не авторизован, ищем всех пользователей с похожими идентификаторами
|
||||||
const identities = {
|
const identities = {
|
||||||
email: email,
|
email: email,
|
||||||
guest: session.guestId
|
guest: session.guestId,
|
||||||
};
|
};
|
||||||
|
|
||||||
const relatedUsers = await authService.identityService.findRelatedUsers(identities);
|
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) {
|
if (relatedUsers.length > 0) {
|
||||||
// Берем первого найденного пользователя как основного
|
// Берем первого найденного пользователя как основного
|
||||||
@@ -124,13 +130,17 @@ class EmailAuth {
|
|||||||
// Мигрируем данные от остальных пользователей к основному
|
// Мигрируем данные от остальных пользователей к основному
|
||||||
for (const userId of relatedUsers.slice(1)) {
|
for (const userId of relatedUsers.slice(1)) {
|
||||||
await authService.identityService.migrateUserData(userId, finalUserId);
|
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)) {
|
if (session.tempUserId && !relatedUsers.includes(session.tempUserId)) {
|
||||||
await authService.identityService.migrateUserData(session.tempUserId, finalUserId);
|
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 {
|
} else {
|
||||||
// Если связанных пользователей нет, используем временного или создаем нового
|
// Если связанных пользователей нет, используем временного или создаем нового
|
||||||
@@ -154,7 +164,9 @@ class EmailAuth {
|
|||||||
// Если есть гостевой ID, добавляем его тоже
|
// Если есть гостевой ID, добавляем его тоже
|
||||||
if (session.guestId) {
|
if (session.guestId) {
|
||||||
await authService.identityService.saveIdentity(finalUserId, 'guest', session.guestId, true);
|
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 {
|
return {
|
||||||
verified: true,
|
verified: true,
|
||||||
userId: finalUserId,
|
userId: finalUserId,
|
||||||
email: email
|
email: email,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error checking email verification:', error);
|
logger.error('Error checking email verification:', error);
|
||||||
@@ -177,4 +189,4 @@ class EmailAuth {
|
|||||||
|
|
||||||
// Создаем и экспортируем единственный экземпляр
|
// Создаем и экспортируем единственный экземпляр
|
||||||
const emailAuth = new EmailAuth();
|
const emailAuth = new EmailAuth();
|
||||||
module.exports = emailAuth;
|
module.exports = emailAuth;
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ const transporter = nodemailer.createTransport({
|
|||||||
maxConnections: 3,
|
maxConnections: 3,
|
||||||
maxMessages: 5,
|
maxMessages: 5,
|
||||||
tls: {
|
tls: {
|
||||||
rejectUnauthorized: false
|
rejectUnauthorized: false,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Конфигурация для получения писем
|
// Конфигурация для получения писем
|
||||||
@@ -31,11 +31,11 @@ const imapConfig = {
|
|||||||
port: process.env.EMAIL_IMAP_PORT,
|
port: process.env.EMAIL_IMAP_PORT,
|
||||||
tls: true,
|
tls: true,
|
||||||
tlsOptions: { rejectUnauthorized: false },
|
tlsOptions: { rejectUnauthorized: false },
|
||||||
keepalive: {
|
keepalive: {
|
||||||
interval: 10000,
|
interval: 10000,
|
||||||
idleInterval: 300000,
|
idleInterval: 300000,
|
||||||
forceNoop: true
|
forceNoop: true,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
class EmailBotService {
|
class EmailBotService {
|
||||||
@@ -66,7 +66,7 @@ class EmailBotService {
|
|||||||
try {
|
try {
|
||||||
// Отправляем код на email
|
// Отправляем код на email
|
||||||
await this.sendVerificationCode(email, code);
|
await this.sendVerificationCode(email, code);
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error initializing email verification:', error);
|
logger.error('Error initializing email verification:', error);
|
||||||
@@ -91,7 +91,7 @@ class EmailBotService {
|
|||||||
</div>
|
</div>
|
||||||
<p style="font-size: 14px; color: #999;">Код действителен в течение 15 минут.</p>
|
<p style="font-size: 14px; color: #999;">Код действителен в течение 15 минут.</p>
|
||||||
</div>
|
</div>
|
||||||
`
|
`,
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.transporter.sendMail(mailOptions);
|
await this.transporter.sendMail(mailOptions);
|
||||||
@@ -121,7 +121,7 @@ class EmailBotService {
|
|||||||
this.imap.end();
|
this.imap.end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ищем непрочитанные письма
|
// Ищем непрочитанные письма
|
||||||
this.imap.search(['UNSEEN'], (err, results) => {
|
this.imap.search(['UNSEEN'], (err, results) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -129,16 +129,16 @@ class EmailBotService {
|
|||||||
this.imap.end();
|
this.imap.end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!results || results.length === 0) {
|
if (!results || results.length === 0) {
|
||||||
logger.info('No new messages found');
|
logger.info('No new messages found');
|
||||||
this.imap.end();
|
this.imap.end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const f = this.imap.fetch(results, { bodies: '' });
|
const f = this.imap.fetch(results, { bodies: '' });
|
||||||
|
|
||||||
f.on('message', (msg, seqno) => {
|
f.on('message', (msg, seqno) => {
|
||||||
msg.on('body', (stream, info) => {
|
msg.on('body', (stream, info) => {
|
||||||
simpleParser(stream, async (err, parsed) => {
|
simpleParser(stream, async (err, parsed) => {
|
||||||
@@ -149,11 +149,11 @@ class EmailBotService {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
f.once('error', (err) => {
|
f.once('error', (err) => {
|
||||||
logger.error(`Fetch error: ${err}`);
|
logger.error(`Fetch error: ${err}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
f.once('end', () => {
|
f.once('end', () => {
|
||||||
try {
|
try {
|
||||||
this.imap.end();
|
this.imap.end();
|
||||||
@@ -172,7 +172,7 @@ class EmailBotService {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.imap.connect();
|
this.imap.connect();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Global error checking emails: ${error.message}`);
|
logger.error(`Global error checking emails: ${error.message}`);
|
||||||
@@ -191,7 +191,7 @@ class EmailBotService {
|
|||||||
from: process.env.EMAIL_USER,
|
from: process.env.EMAIL_USER,
|
||||||
to,
|
to,
|
||||||
subject,
|
subject,
|
||||||
text
|
text,
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.transporter.sendMail(mailOptions);
|
await this.transporter.sendMail(mailOptions);
|
||||||
|
|||||||
@@ -15,19 +15,19 @@ class IdentityService {
|
|||||||
if (!provider || !providerId) {
|
if (!provider || !providerId) {
|
||||||
return { provider, providerId };
|
return { provider, providerId };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Приводим провайдер к нижнему регистру
|
// Приводим провайдер к нижнему регистру
|
||||||
const normalizedProvider = provider.toLowerCase();
|
const normalizedProvider = provider.toLowerCase();
|
||||||
|
|
||||||
// Для email и wallet приводим значение к нижнему регистру
|
// Для email и wallet приводим значение к нижнему регистру
|
||||||
let normalizedProviderId = providerId;
|
let normalizedProviderId = providerId;
|
||||||
if (normalizedProvider === 'wallet' || normalizedProvider === 'email') {
|
if (normalizedProvider === 'wallet' || normalizedProvider === 'email') {
|
||||||
normalizedProviderId = providerId.toLowerCase();
|
normalizedProviderId = providerId.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
provider: normalizedProvider,
|
provider: normalizedProvider,
|
||||||
providerId: normalizedProviderId
|
providerId: normalizedProviderId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,21 +42,25 @@ class IdentityService {
|
|||||||
async saveIdentity(userId, provider, providerId, verified = true) {
|
async saveIdentity(userId, provider, providerId, verified = true) {
|
||||||
try {
|
try {
|
||||||
if (!userId || !provider || !providerId) {
|
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 {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Missing required parameters'
|
error: 'Missing required parameters',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Нормализуем значения
|
// Нормализуем значения
|
||||||
const { provider: normalizedProvider, providerId: normalizedProviderId } =
|
const { provider: normalizedProvider, providerId: normalizedProviderId } =
|
||||||
this.normalizeIdentity(provider, providerId);
|
this.normalizeIdentity(provider, providerId);
|
||||||
|
|
||||||
// Проверяем тип провайдера и перенаправляем гостевые идентификаторы в guest_user_mapping
|
// Проверяем тип провайдера и перенаправляем гостевые идентификаторы в guest_user_mapping
|
||||||
if (normalizedProvider === 'guest') {
|
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 {
|
try {
|
||||||
await db.query(
|
await db.query(
|
||||||
'INSERT INTO guest_user_mapping (user_id, guest_id) VALUES ($1, $2) ON CONFLICT (guest_id) DO UPDATE SET user_id = $1',
|
'INSERT INTO guest_user_mapping (user_id, guest_id) VALUES ($1, $2) ON CONFLICT (guest_id) DO UPDATE SET user_id = $1',
|
||||||
@@ -64,41 +68,50 @@ class IdentityService {
|
|||||||
);
|
);
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (guestError) {
|
} 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 };
|
return { success: false, error: guestError.message };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, разрешен ли такой тип провайдера
|
// Проверяем, разрешен ли такой тип провайдера
|
||||||
const allowedProviders = ['email', 'wallet', 'telegram', 'username'];
|
const allowedProviders = ['email', 'wallet', 'telegram', 'username'];
|
||||||
if (!allowedProviders.includes(normalizedProvider)) {
|
if (!allowedProviders.includes(normalizedProvider)) {
|
||||||
logger.warn(`[IdentityService] Invalid provider type: ${normalizedProvider}`);
|
logger.warn(`[IdentityService] Invalid provider type: ${normalizedProvider}`);
|
||||||
return {
|
return {
|
||||||
success: false,
|
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(
|
const existingResult = await db.query(
|
||||||
`SELECT user_id FROM user_identities WHERE provider = $1 AND provider_id = $2`,
|
`SELECT user_id FROM user_identities WHERE provider = $1 AND provider_id = $2`,
|
||||||
[normalizedProvider, normalizedProviderId]
|
[normalizedProvider, normalizedProviderId]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (existingResult.rows.length > 0) {
|
if (existingResult.rows.length > 0) {
|
||||||
const existingUserId = existingResult.rows[0].user_id;
|
const existingUserId = existingResult.rows[0].user_id;
|
||||||
|
|
||||||
// Если идентификатор уже принадлежит этому пользователю, ничего не делаем
|
// Если идентификатор уже принадлежит этому пользователю, ничего не делаем
|
||||||
if (existingUserId === userId) {
|
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 {
|
} 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 {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: `Identity already belongs to another user (${existingUserId})`
|
error: `Identity already belongs to another user (${existingUserId})`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -108,16 +121,21 @@ class IdentityService {
|
|||||||
VALUES ($1, $2, $3)`,
|
VALUES ($1, $2, $3)`,
|
||||||
[userId, normalizedProvider, normalizedProviderId]
|
[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 };
|
return { success: true };
|
||||||
} catch (error) {
|
} 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 };
|
return { success: false, error: error.message };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Получает все идентификаторы пользователя
|
* Получает все идентификаторы пользователя
|
||||||
* @param {number} userId - ID пользователя
|
* @param {number} userId - ID пользователя
|
||||||
@@ -129,12 +147,12 @@ class IdentityService {
|
|||||||
logger.warn('[IdentityService] Missing userId parameter');
|
logger.warn('[IdentityService] Missing userId parameter');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await db.query(
|
const result = await db.query(
|
||||||
`SELECT provider, provider_id FROM user_identities WHERE user_id = $1`,
|
`SELECT provider, provider_id FROM user_identities WHERE user_id = $1`,
|
||||||
[userId]
|
[userId]
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.info(`[IdentityService] Found ${result.rows.length} identities for user ${userId}`);
|
logger.info(`[IdentityService] Found ${result.rows.length} identities for user ${userId}`);
|
||||||
return result.rows;
|
return result.rows;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -142,7 +160,7 @@ class IdentityService {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Получает все идентификаторы пользователя определенного типа
|
* Получает все идентификаторы пользователя определенного типа
|
||||||
* @param {number} userId - ID пользователя
|
* @param {number} userId - ID пользователя
|
||||||
@@ -155,20 +173,25 @@ class IdentityService {
|
|||||||
logger.warn(`[IdentityService] Missing parameters: userId=${userId}, provider=${provider}`);
|
logger.warn(`[IdentityService] Missing parameters: userId=${userId}, provider=${provider}`);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await db.query(
|
const result = await db.query(
|
||||||
`SELECT provider_id FROM user_identities WHERE user_id = $1 AND provider = $2`,
|
`SELECT provider_id FROM user_identities WHERE user_id = $1 AND provider = $2`,
|
||||||
[userId, provider]
|
[userId, provider]
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.info(`[IdentityService] Found ${result.rows.length} ${provider} identities for user ${userId}`);
|
logger.info(
|
||||||
return result.rows.map(row => row.provider_id);
|
`[IdentityService] Found ${result.rows.length} ${provider} identities for user ${userId}`
|
||||||
|
);
|
||||||
|
return result.rows.map((row) => row.provider_id);
|
||||||
} catch (error) {
|
} 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 [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Находит пользователя по идентификатору
|
* Находит пользователя по идентификатору
|
||||||
* @param {string} provider - Тип идентификатора
|
* @param {string} provider - Тип идентификатора
|
||||||
@@ -178,34 +201,43 @@ class IdentityService {
|
|||||||
async findUserByIdentity(provider, providerId) {
|
async findUserByIdentity(provider, providerId) {
|
||||||
try {
|
try {
|
||||||
if (!provider || !providerId) {
|
if (!provider || !providerId) {
|
||||||
logger.warn(`[IdentityService] Missing parameters: provider=${provider}, providerId=${providerId}`);
|
logger.warn(
|
||||||
|
`[IdentityService] Missing parameters: provider=${provider}, providerId=${providerId}`
|
||||||
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Нормализуем значения
|
// Нормализуем значения
|
||||||
const { provider: normalizedProvider, providerId: normalizedProviderId } =
|
const { provider: normalizedProvider, providerId: normalizedProviderId } =
|
||||||
this.normalizeIdentity(provider, providerId);
|
this.normalizeIdentity(provider, providerId);
|
||||||
|
|
||||||
const result = await db.query(
|
const result = await db.query(
|
||||||
`SELECT u.id, u.role FROM users u
|
`SELECT u.id, u.role FROM users u
|
||||||
JOIN user_identities ui ON u.id = ui.user_id
|
JOIN user_identities ui ON u.id = ui.user_id
|
||||||
WHERE ui.provider = $1 AND ui.provider_id = $2`,
|
WHERE ui.provider = $1 AND ui.provider_id = $2`,
|
||||||
[normalizedProvider, normalizedProviderId]
|
[normalizedProvider, normalizedProviderId]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.rows.length === 0) {
|
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;
|
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];
|
return result.rows[0];
|
||||||
} catch (error) {
|
} 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;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Сохраняет идентификаторы из сессии для пользователя
|
* Сохраняет идентификаторы из сессии для пользователя
|
||||||
* @param {object} session - Объект сессии
|
* @param {object} session - Объект сессии
|
||||||
@@ -218,25 +250,30 @@ class IdentityService {
|
|||||||
logger.warn(`[IdentityService] Missing parameters: session=${!!session}, userId=${userId}`);
|
logger.warn(`[IdentityService] Missing parameters: session=${!!session}, userId=${userId}`);
|
||||||
return { success: false, error: 'Missing required parameters' };
|
return { success: false, error: 'Missing required parameters' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = [];
|
const results = [];
|
||||||
|
|
||||||
// Сохраняем все постоянные идентификаторы из сессии
|
// Сохраняем все постоянные идентификаторы из сессии
|
||||||
if (session.email) {
|
if (session.email) {
|
||||||
const emailResult = await this.saveIdentity(userId, 'email', session.email, true);
|
const emailResult = await this.saveIdentity(userId, 'email', session.email, true);
|
||||||
results.push({ type: 'email', result: emailResult });
|
results.push({ type: 'email', result: emailResult });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session.address) {
|
if (session.address) {
|
||||||
const walletResult = await this.saveIdentity(userId, 'wallet', session.address, true);
|
const walletResult = await this.saveIdentity(userId, 'wallet', session.address, true);
|
||||||
results.push({ type: 'wallet', result: walletResult });
|
results.push({ type: 'wallet', result: walletResult });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session.telegramId) {
|
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 });
|
results.push({ type: 'telegram', result: telegramResult });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем гостевые идентификаторы в guest_user_mapping
|
// Сохраняем гостевые идентификаторы в guest_user_mapping
|
||||||
if (session.guestId) {
|
if (session.guestId) {
|
||||||
try {
|
try {
|
||||||
@@ -250,7 +287,7 @@ class IdentityService {
|
|||||||
results.push({ type: 'guest', result: { success: false, error: error.message } });
|
results.push({ type: 'guest', result: { success: false, error: error.message } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session.previousGuestId && session.previousGuestId !== session.guestId) {
|
if (session.previousGuestId && session.previousGuestId !== session.guestId) {
|
||||||
try {
|
try {
|
||||||
await db.query(
|
await db.query(
|
||||||
@@ -259,19 +296,27 @@ class IdentityService {
|
|||||||
);
|
);
|
||||||
results.push({ type: 'previousGuest', result: { success: true } });
|
results.push({ type: 'previousGuest', result: { success: true } });
|
||||||
} catch (error) {
|
} 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 } });
|
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 };
|
return { success: true, results };
|
||||||
} catch (error) {
|
} 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 };
|
return { success: false, error: error.message };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Мигрирует все идентификаторы и сообщения от одного пользователя к другому
|
* Мигрирует все идентификаторы и сообщения от одного пользователя к другому
|
||||||
* @param {number} fromUserId - ID исходного пользователя
|
* @param {number} fromUserId - ID исходного пользователя
|
||||||
@@ -281,7 +326,9 @@ class IdentityService {
|
|||||||
async migrateUserData(fromUserId, toUserId) {
|
async migrateUserData(fromUserId, toUserId) {
|
||||||
try {
|
try {
|
||||||
if (!fromUserId || !toUserId) {
|
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' };
|
return { success: false, error: 'Missing required parameters' };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,7 +342,7 @@ class IdentityService {
|
|||||||
`SELECT provider, provider_id FROM user_identities WHERE user_id = $1`,
|
`SELECT provider, provider_id FROM user_identities WHERE user_id = $1`,
|
||||||
[fromUserId]
|
[fromUserId]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Переносим каждый идентификатор
|
// Переносим каждый идентификатор
|
||||||
for (const identity of identitiesResult.rows) {
|
for (const identity of identitiesResult.rows) {
|
||||||
await client.query(
|
await client.query(
|
||||||
@@ -304,7 +351,7 @@ class IdentityService {
|
|||||||
ON CONFLICT (provider, provider_id) DO NOTHING`,
|
ON CONFLICT (provider, provider_id) DO NOTHING`,
|
||||||
[toUserId, identity.provider, identity.provider_id]
|
[toUserId, identity.provider, identity.provider_id]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Удаляем старый идентификатор
|
// Удаляем старый идентификатор
|
||||||
await client.query(
|
await client.query(
|
||||||
`DELETE FROM user_identities
|
`DELETE FROM user_identities
|
||||||
@@ -312,13 +359,13 @@ class IdentityService {
|
|||||||
[fromUserId, identity.provider, identity.provider_id]
|
[fromUserId, identity.provider, identity.provider_id]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Мигрируем гостевые идентификаторы из новой таблицы guest_user_mapping
|
// Мигрируем гостевые идентификаторы из новой таблицы guest_user_mapping
|
||||||
const guestMappingsResult = await client.query(
|
const guestMappingsResult = await client.query(
|
||||||
`SELECT guest_id, processed FROM guest_user_mapping WHERE user_id = $1`,
|
`SELECT guest_id, processed FROM guest_user_mapping WHERE user_id = $1`,
|
||||||
[fromUserId]
|
[fromUserId]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Переносим каждый гостевой идентификатор
|
// Переносим каждый гостевой идентификатор
|
||||||
for (const mapping of guestMappingsResult.rows) {
|
for (const mapping of guestMappingsResult.rows) {
|
||||||
await client.query(
|
await client.query(
|
||||||
@@ -329,12 +376,9 @@ class IdentityService {
|
|||||||
[toUserId, mapping.guest_id, mapping.processed]
|
[toUserId, mapping.guest_id, mapping.processed]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Удаляем старые гостевые маппинги
|
// Удаляем старые гостевые маппинги
|
||||||
await client.query(
|
await client.query(`DELETE FROM guest_user_mapping WHERE user_id = $1`, [fromUserId]);
|
||||||
`DELETE FROM guest_user_mapping WHERE user_id = $1`,
|
|
||||||
[fromUserId]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Переносим все сообщения
|
// Переносим все сообщения
|
||||||
await client.query(
|
await client.query(
|
||||||
@@ -351,7 +395,7 @@ class IdentityService {
|
|||||||
WHERE user_id = $2`,
|
WHERE user_id = $2`,
|
||||||
[toUserId, fromUserId]
|
[toUserId, fromUserId]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Переносим настройки пользователя
|
// Переносим настройки пользователя
|
||||||
await client.query(
|
await client.query(
|
||||||
`UPDATE user_preferences
|
`UPDATE user_preferences
|
||||||
@@ -362,8 +406,10 @@ class IdentityService {
|
|||||||
|
|
||||||
// Завершаем транзакцию
|
// Завершаем транзакцию
|
||||||
await client.query('COMMIT');
|
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 };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await client.query('ROLLBACK');
|
await client.query('ROLLBACK');
|
||||||
@@ -386,20 +432,20 @@ class IdentityService {
|
|||||||
async findRelatedUsers(identities) {
|
async findRelatedUsers(identities) {
|
||||||
try {
|
try {
|
||||||
const userIds = new Set();
|
const userIds = new Set();
|
||||||
|
|
||||||
for (const [provider, providerId] of Object.entries(identities)) {
|
for (const [provider, providerId] of Object.entries(identities)) {
|
||||||
if (!providerId) continue;
|
if (!providerId) continue;
|
||||||
|
|
||||||
const result = await db.query(
|
const result = await db.query(
|
||||||
`SELECT DISTINCT user_id
|
`SELECT DISTINCT user_id
|
||||||
FROM user_identities
|
FROM user_identities
|
||||||
WHERE provider = $1 AND provider_id = $2`,
|
WHERE provider = $1 AND provider_id = $2`,
|
||||||
[provider, providerId]
|
[provider, providerId]
|
||||||
);
|
);
|
||||||
|
|
||||||
result.rows.forEach(row => userIds.add(row.user_id));
|
result.rows.forEach((row) => userIds.add(row.user_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.from(userIds);
|
return Array.from(userIds);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`[IdentityService] Error finding related users:`, error);
|
logger.error(`[IdentityService] Error finding related users:`, error);
|
||||||
@@ -408,4 +454,4 @@ class IdentityService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = new IdentityService();
|
module.exports = new IdentityService();
|
||||||
|
|||||||
@@ -31,5 +31,5 @@ module.exports = {
|
|||||||
getConversationHistory: aiAssistant.getConversationHistory,
|
getConversationHistory: aiAssistant.getConversationHistory,
|
||||||
|
|
||||||
telegramBot,
|
telegramBot,
|
||||||
aiAssistant
|
aiAssistant,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class SessionService {
|
|||||||
async saveSession(session) {
|
async saveSession(session) {
|
||||||
try {
|
try {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
session.save(err => {
|
session.save((err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
logger.error('Error saving session:', err);
|
logger.error('Error saving session:', err);
|
||||||
reject(err);
|
reject(err);
|
||||||
@@ -38,7 +38,9 @@ class SessionService {
|
|||||||
*/
|
*/
|
||||||
async linkGuestMessages(session, userId) {
|
async linkGuestMessages(session, userId) {
|
||||||
try {
|
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, если его нет
|
// Инициализируем массив обработанных гостевых ID, если его нет
|
||||||
if (!session.processedGuestIds) {
|
if (!session.processedGuestIds) {
|
||||||
@@ -50,15 +52,15 @@ class SessionService {
|
|||||||
'SELECT guest_id FROM guest_user_mapping WHERE user_id = $1',
|
'SELECT guest_id FROM guest_user_mapping WHERE user_id = $1',
|
||||||
[userId]
|
[userId]
|
||||||
);
|
);
|
||||||
const userGuestIds = guestIdsResult.rows.map(row => row.guest_id);
|
const userGuestIds = guestIdsResult.rows.map((row) => row.guest_id);
|
||||||
|
|
||||||
// Собираем все гостевые ID, которые нужно обработать
|
// Собираем все гостевые ID, которые нужно обработать
|
||||||
const guestIdsToProcess = new Set();
|
const guestIdsToProcess = new Set();
|
||||||
|
|
||||||
// Добавляем текущий гостевой ID
|
// Добавляем текущий гостевой ID
|
||||||
if (session.guestId && !session.processedGuestIds.includes(session.guestId)) {
|
if (session.guestId && !session.processedGuestIds.includes(session.guestId)) {
|
||||||
guestIdsToProcess.add(session.guestId);
|
guestIdsToProcess.add(session.guestId);
|
||||||
|
|
||||||
// Записываем связь с пользователем в новую таблицу
|
// Записываем связь с пользователем в новую таблицу
|
||||||
await db.query(
|
await db.query(
|
||||||
'INSERT INTO guest_user_mapping (user_id, guest_id) VALUES ($1, $2) ON CONFLICT (guest_id) DO UPDATE SET user_id = $1',
|
'INSERT INTO guest_user_mapping (user_id, guest_id) VALUES ($1, $2) ON CONFLICT (guest_id) DO UPDATE SET user_id = $1',
|
||||||
@@ -69,7 +71,7 @@ class SessionService {
|
|||||||
// Добавляем предыдущий гостевой ID
|
// Добавляем предыдущий гостевой ID
|
||||||
if (session.previousGuestId && !session.processedGuestIds.includes(session.previousGuestId)) {
|
if (session.previousGuestId && !session.processedGuestIds.includes(session.previousGuestId)) {
|
||||||
guestIdsToProcess.add(session.previousGuestId);
|
guestIdsToProcess.add(session.previousGuestId);
|
||||||
|
|
||||||
// Записываем связь с пользователем в новую таблицу
|
// Записываем связь с пользователем в новую таблицу
|
||||||
await db.query(
|
await db.query(
|
||||||
'INSERT INTO guest_user_mapping (user_id, guest_id) VALUES ($1, $2) ON CONFLICT (guest_id) DO UPDATE SET user_id = $1',
|
'INSERT INTO guest_user_mapping (user_id, guest_id) VALUES ($1, $2) ON CONFLICT (guest_id) DO UPDATE SET user_id = $1',
|
||||||
@@ -88,12 +90,11 @@ class SessionService {
|
|||||||
for (const guestId of guestIdsToProcess) {
|
for (const guestId of guestIdsToProcess) {
|
||||||
await this.processGuestMessagesWrapper(userId, guestId);
|
await this.processGuestMessagesWrapper(userId, guestId);
|
||||||
session.processedGuestIds.push(guestId);
|
session.processedGuestIds.push(guestId);
|
||||||
|
|
||||||
// Помечаем guestId как обработанный в базе данных
|
// Помечаем guestId как обработанный в базе данных
|
||||||
await db.query(
|
await db.query('UPDATE guest_user_mapping SET processed = true WHERE guest_id = $1', [
|
||||||
'UPDATE guest_user_mapping SET processed = true WHERE guest_id = $1',
|
guestId,
|
||||||
[guestId]
|
]);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем сессию
|
// Сохраняем сессию
|
||||||
@@ -114,7 +115,9 @@ class SessionService {
|
|||||||
*/
|
*/
|
||||||
async processGuestMessagesWrapper(userId, guestId) {
|
async processGuestMessagesWrapper(userId, guestId) {
|
||||||
try {
|
try {
|
||||||
logger.info(`[processGuestMessagesWrapper] Processing messages: userId=${userId}, guestId=${guestId}`);
|
logger.info(
|
||||||
|
`[processGuestMessagesWrapper] Processing messages: userId=${userId}, guestId=${guestId}`
|
||||||
|
);
|
||||||
return await processGuestMessages(userId, guestId);
|
return await processGuestMessages(userId, guestId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`[processGuestMessagesWrapper] Error: ${error.message}`, error);
|
logger.error(`[processGuestMessagesWrapper] Error: ${error.message}`, error);
|
||||||
@@ -146,7 +149,7 @@ class SessionService {
|
|||||||
async destroySession(session) {
|
async destroySession(session) {
|
||||||
try {
|
try {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
session.destroy(err => {
|
session.destroy((err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
logger.error('Error destroying session:', err);
|
logger.error('Error destroying session:', err);
|
||||||
reject(err);
|
reject(err);
|
||||||
@@ -173,29 +176,26 @@ class SessionService {
|
|||||||
logger.warn('[SessionService] Cannot restore session without sessionId');
|
logger.warn('[SessionService] Cannot restore session without sessionId');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`[SessionService] Attempting to retrieve session ${sessionId}`);
|
logger.info(`[SessionService] Attempting to retrieve session ${sessionId}`);
|
||||||
|
|
||||||
const result = await db.query(
|
const result = await db.query('SELECT sess FROM session WHERE sid = $1', [sessionId]);
|
||||||
'SELECT sess FROM session WHERE sid = $1',
|
|
||||||
[sessionId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.rows.length === 0) {
|
if (result.rows.length === 0) {
|
||||||
logger.info(`[SessionService] No session found with ID ${sessionId}`);
|
logger.info(`[SessionService] No session found with ID ${sessionId}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionData = result.rows[0].sess;
|
const sessionData = result.rows[0].sess;
|
||||||
logger.info(`[SessionService] Retrieved session data for ${sessionId}`);
|
logger.info(`[SessionService] Retrieved session data for ${sessionId}`);
|
||||||
|
|
||||||
return sessionData;
|
return sessionData;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`[SessionService] Error retrieving session ${sessionId}:`, error);
|
logger.error(`[SessionService] Error retrieving session ${sessionId}:`, error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Обновляет данные аутентификации в сессии
|
* Обновляет данные аутентификации в сессии
|
||||||
* @param {object} session - Объект сессии
|
* @param {object} session - Объект сессии
|
||||||
@@ -208,23 +208,23 @@ class SessionService {
|
|||||||
logger.warn('[SessionService] Missing parameters for updateAuthData');
|
logger.warn('[SessionService] Missing parameters for updateAuthData');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { userId, authType, isAdmin, ...otherData } = authData;
|
const { userId, authType, isAdmin, ...otherData } = authData;
|
||||||
|
|
||||||
if (!userId || !authType) {
|
if (!userId || !authType) {
|
||||||
logger.warn('[SessionService] Missing userId or authType in authData');
|
logger.warn('[SessionService] Missing userId or authType in authData');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем основные поля аутентификации
|
// Обновляем основные поля аутентификации
|
||||||
session.userId = userId;
|
session.userId = userId;
|
||||||
session.authType = authType;
|
session.authType = authType;
|
||||||
session.authenticated = true;
|
session.authenticated = true;
|
||||||
|
|
||||||
if (isAdmin !== undefined) {
|
if (isAdmin !== undefined) {
|
||||||
session.isAdmin = isAdmin;
|
session.isAdmin = isAdmin;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем дополнительные данные в зависимости от типа аутентификации
|
// Обновляем дополнительные данные в зависимости от типа аутентификации
|
||||||
if (authType === 'wallet' && otherData.address) {
|
if (authType === 'wallet' && otherData.address) {
|
||||||
session.address = otherData.address.toLowerCase();
|
session.address = otherData.address.toLowerCase();
|
||||||
@@ -235,16 +235,16 @@ class SessionService {
|
|||||||
if (otherData.telegramUsername) session.telegramUsername = otherData.telegramUsername;
|
if (otherData.telegramUsername) session.telegramUsername = otherData.telegramUsername;
|
||||||
if (otherData.telegramFirstName) session.telegramFirstName = otherData.telegramFirstName;
|
if (otherData.telegramFirstName) session.telegramFirstName = otherData.telegramFirstName;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем гостевые ID, если они предоставлены и не были ранее в сессии
|
// Сохраняем гостевые ID, если они предоставлены и не были ранее в сессии
|
||||||
if (otherData.guestId && !session.guestId) {
|
if (otherData.guestId && !session.guestId) {
|
||||||
session.guestId = otherData.guestId;
|
session.guestId = otherData.guestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (otherData.previousGuestId && !session.previousGuestId) {
|
if (otherData.previousGuestId && !session.previousGuestId) {
|
||||||
session.previousGuestId = otherData.previousGuestId;
|
session.previousGuestId = otherData.previousGuestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем обновленную сессию
|
// Сохраняем обновленную сессию
|
||||||
return await this.saveSession(session, 'updateAuthData');
|
return await this.saveSession(session, 'updateAuthData');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -252,7 +252,7 @@ class SessionService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Очищает данные аутентификации в сессии
|
* Очищает данные аутентификации в сессии
|
||||||
* @param {object} session - Объект сессии
|
* @param {object} session - Объект сессии
|
||||||
@@ -264,10 +264,10 @@ class SessionService {
|
|||||||
logger.warn('[SessionService] Cannot logout null session');
|
logger.warn('[SessionService] Cannot logout null session');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем гостевые ID перед очисткой
|
// Сохраняем гостевые ID перед очисткой
|
||||||
const guestId = session.guestId;
|
const guestId = session.guestId;
|
||||||
|
|
||||||
// Удаляем данные аутентификации
|
// Удаляем данные аутентификации
|
||||||
delete session.userId;
|
delete session.userId;
|
||||||
delete session.authenticated;
|
delete session.authenticated;
|
||||||
@@ -278,12 +278,12 @@ class SessionService {
|
|||||||
delete session.telegramId;
|
delete session.telegramId;
|
||||||
delete session.telegramUsername;
|
delete session.telegramUsername;
|
||||||
delete session.telegramFirstName;
|
delete session.telegramFirstName;
|
||||||
|
|
||||||
// Восстанавливаем гостевой ID для продолжения работы
|
// Восстанавливаем гостевой ID для продолжения работы
|
||||||
if (guestId) {
|
if (guestId) {
|
||||||
session.guestId = guestId;
|
session.guestId = guestId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем обновленную сессию
|
// Сохраняем обновленную сессию
|
||||||
return await this.saveSession(session, 'logout');
|
return await this.saveSession(session, 'logout');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -294,4 +294,4 @@ class SessionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sessionService = new SessionService();
|
const sessionService = new SessionService();
|
||||||
module.exports = sessionService;
|
module.exports = sessionService;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ async function getBot() {
|
|||||||
// Обработка кодов верификации
|
// Обработка кодов верификации
|
||||||
botInstance.on('text', async (ctx) => {
|
botInstance.on('text', async (ctx) => {
|
||||||
const code = ctx.message.text.trim();
|
const code = ctx.message.text.trim();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Получаем код верификации для всех активных кодов с провайдером telegram
|
// Получаем код верификации для всех активных кодов с провайдером telegram
|
||||||
const codeResult = await db.query(
|
const codeResult = await db.query(
|
||||||
@@ -31,25 +31,24 @@ async function getBot() {
|
|||||||
AND expires_at > NOW()`,
|
AND expires_at > NOW()`,
|
||||||
[code]
|
[code]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (codeResult.rows.length === 0) {
|
if (codeResult.rows.length === 0) {
|
||||||
ctx.reply('Неверный код подтверждения');
|
ctx.reply('Неверный код подтверждения');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const verification = codeResult.rows[0];
|
const verification = codeResult.rows[0];
|
||||||
const providerId = verification.provider_id;
|
const providerId = verification.provider_id;
|
||||||
const linkedUserId = verification.user_id; // Получаем связанный userId если он есть
|
const linkedUserId = verification.user_id; // Получаем связанный userId если он есть
|
||||||
let userId;
|
let userId;
|
||||||
|
|
||||||
// Отмечаем код как использованный
|
// Отмечаем код как использованный
|
||||||
await db.query(
|
await db.query('UPDATE verification_codes SET used = true WHERE id = $1', [
|
||||||
'UPDATE verification_codes SET used = true WHERE id = $1',
|
verification.id,
|
||||||
[verification.id]
|
]);
|
||||||
);
|
|
||||||
|
|
||||||
logger.info('Starting Telegram auth process for code:', code);
|
logger.info('Starting Telegram auth process for code:', code);
|
||||||
|
|
||||||
// Проверяем, существует ли уже пользователь с таким Telegram ID
|
// Проверяем, существует ли уже пользователь с таким Telegram ID
|
||||||
const existingTelegramUser = await db.query(
|
const existingTelegramUser = await db.query(
|
||||||
`SELECT ui.user_id
|
`SELECT ui.user_id
|
||||||
@@ -57,7 +56,7 @@ async function getBot() {
|
|||||||
WHERE ui.provider = 'telegram' AND ui.provider_id = $1`,
|
WHERE ui.provider = 'telegram' AND ui.provider_id = $1`,
|
||||||
[ctx.from.id.toString()]
|
[ctx.from.id.toString()]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (existingTelegramUser.rows.length > 0) {
|
if (existingTelegramUser.rows.length > 0) {
|
||||||
// Если пользователь с таким Telegram ID уже существует, используем его
|
// Если пользователь с таким Telegram ID уже существует, используем его
|
||||||
userId = existingTelegramUser.rows[0].user_id;
|
userId = existingTelegramUser.rows[0].user_id;
|
||||||
@@ -74,7 +73,9 @@ async function getBot() {
|
|||||||
VALUES ($1, $2, $3, NOW())`,
|
VALUES ($1, $2, $3, NOW())`,
|
||||||
[userId, 'telegram', ctx.from.id.toString()]
|
[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 {
|
} else {
|
||||||
// Проверяем, есть ли пользователь, связанный с гостевым идентификатором
|
// Проверяем, есть ли пользователь, связанный с гостевым идентификатором
|
||||||
let existingUserWithGuestId = null;
|
let existingUserWithGuestId = null;
|
||||||
@@ -85,10 +86,12 @@ async function getBot() {
|
|||||||
);
|
);
|
||||||
if (guestUserResult.rows.length > 0) {
|
if (guestUserResult.rows.length > 0) {
|
||||||
existingUserWithGuestId = guestUserResult.rows[0].user_id;
|
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}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existingUserWithGuestId) {
|
if (existingUserWithGuestId) {
|
||||||
// Используем существующего пользователя и добавляем ему Telegram идентификатор
|
// Используем существующего пользователя и добавляем ему Telegram идентификатор
|
||||||
userId = existingUserWithGuestId;
|
userId = existingUserWithGuestId;
|
||||||
@@ -106,7 +109,7 @@ async function getBot() {
|
|||||||
['user']
|
['user']
|
||||||
);
|
);
|
||||||
userId = userResult.rows[0].id;
|
userId = userResult.rows[0].id;
|
||||||
|
|
||||||
// Связываем Telegram с новым пользователем
|
// Связываем Telegram с новым пользователем
|
||||||
await db.query(
|
await db.query(
|
||||||
`INSERT INTO user_identities
|
`INSERT INTO user_identities
|
||||||
@@ -114,7 +117,7 @@ async function getBot() {
|
|||||||
VALUES ($1, $2, $3, NOW())`,
|
VALUES ($1, $2, $3, NOW())`,
|
||||||
[userId, 'telegram', ctx.from.id.toString()]
|
[userId, 'telegram', ctx.from.id.toString()]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Если был гостевой ID, связываем его с новым пользователем
|
// Если был гостевой ID, связываем его с новым пользователем
|
||||||
if (providerId) {
|
if (providerId) {
|
||||||
await db.query(
|
await db.query(
|
||||||
@@ -125,12 +128,12 @@ async function getBot() {
|
|||||||
[userId, providerId]
|
[userId, providerId]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Created new user ${userId} with Telegram account ${ctx.from.id}`);
|
logger.info(`Created new user ${userId} with Telegram account ${ctx.from.id}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем сессию в базе данных
|
// Обновляем сессию в базе данных
|
||||||
await db.query(
|
await db.query(
|
||||||
`UPDATE session
|
`UPDATE session
|
||||||
@@ -140,23 +143,22 @@ async function getBot() {
|
|||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
userId: userId.toString(),
|
userId: userId.toString(),
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
authType: "telegram",
|
authType: 'telegram',
|
||||||
telegramId: ctx.from.id.toString()
|
telegramId: ctx.from.id.toString(),
|
||||||
}),
|
}),
|
||||||
JSON.stringify({guestId: providerId})
|
JSON.stringify({ guestId: providerId }),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Отправляем сообщение об успешной аутентификации
|
// Отправляем сообщение об успешной аутентификации
|
||||||
await ctx.reply('Аутентификация успешна! Можете вернуться в приложение.');
|
await ctx.reply('Аутентификация успешна! Можете вернуться в приложение.');
|
||||||
|
|
||||||
// Удаляем сообщение с кодом
|
// Удаляем сообщение с кодом
|
||||||
try {
|
try {
|
||||||
await ctx.deleteMessage(ctx.message.message_id);
|
await ctx.deleteMessage(ctx.message.message_id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn('Could not delete code message:', error);
|
logger.warn('Could not delete code message:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error in Telegram auth:', error);
|
logger.error('Error in Telegram auth:', error);
|
||||||
await ctx.reply('Произошла ошибка при аутентификации. Попробуйте позже.');
|
await ctx.reply('Произошла ошибка при аутентификации. Попробуйте позже.');
|
||||||
@@ -166,7 +168,7 @@ async function getBot() {
|
|||||||
// Запускаем бота
|
// Запускаем бота
|
||||||
await botInstance.launch();
|
await botInstance.launch();
|
||||||
}
|
}
|
||||||
|
|
||||||
return botInstance;
|
return botInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,12 +192,12 @@ async function initTelegramAuth(session) {
|
|||||||
// Используем временный идентификатор для создания кода верификации
|
// Используем временный идентификатор для создания кода верификации
|
||||||
// Реальный пользователь будет создан или найден при проверке кода через бота
|
// Реальный пользователь будет создан или найден при проверке кода через бота
|
||||||
const tempId = crypto.randomBytes(16).toString('hex');
|
const tempId = crypto.randomBytes(16).toString('hex');
|
||||||
|
|
||||||
// Если пользователь уже авторизован, сохраняем его userId в guest_user_mapping
|
// Если пользователь уже авторизован, сохраняем его userId в guest_user_mapping
|
||||||
// чтобы потом при авторизации через бота этот пользователь был найден
|
// чтобы потом при авторизации через бота этот пользователь был найден
|
||||||
if (session && session.authenticated && session.userId) {
|
if (session && session.authenticated && session.userId) {
|
||||||
const guestId = session.guestId || tempId;
|
const guestId = session.guestId || tempId;
|
||||||
|
|
||||||
// Связываем гостевой ID с текущим пользователем
|
// Связываем гостевой ID с текущим пользователем
|
||||||
await db.query(
|
await db.query(
|
||||||
`INSERT INTO guest_user_mapping (user_id, guest_id)
|
`INSERT INTO guest_user_mapping (user_id, guest_id)
|
||||||
@@ -203,22 +205,26 @@ async function initTelegramAuth(session) {
|
|||||||
ON CONFLICT (guest_id) DO UPDATE SET user_id = $1`,
|
ON CONFLICT (guest_id) DO UPDATE SET user_id = $1`,
|
||||||
[session.userId, guestId]
|
[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}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Создаем код через сервис верификации с идентификатором
|
// Создаем код через сервис верификации с идентификатором
|
||||||
const code = await verificationService.createVerificationCode(
|
const code = await verificationService.createVerificationCode(
|
||||||
'telegram',
|
'telegram',
|
||||||
session.guestId || tempId,
|
session.guestId || tempId,
|
||||||
session.authenticated ? session.userId : null
|
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 {
|
return {
|
||||||
verificationCode: code,
|
verificationCode: code,
|
||||||
botLink: `https://t.me/${process.env.TELEGRAM_BOT_USERNAME}`
|
botLink: `https://t.me/${process.env.TELEGRAM_BOT_USERNAME}`,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error initializing Telegram auth:', error);
|
logger.error('Error initializing Telegram auth:', error);
|
||||||
@@ -229,5 +235,5 @@ async function initTelegramAuth(session) {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
getBot,
|
getBot,
|
||||||
stopBot,
|
stopBot,
|
||||||
initTelegramAuth
|
initTelegramAuth,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ class VerificationService {
|
|||||||
|
|
||||||
// Генерация кода
|
// Генерация кода
|
||||||
generateCode() {
|
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}`);
|
logger.info(`Generated verification code: ${code}`);
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
@@ -20,8 +23,10 @@ class VerificationService {
|
|||||||
const expiresAt = new Date(Date.now() + this.expirationMinutes * 60 * 1000);
|
const expiresAt = new Date(Date.now() + this.expirationMinutes * 60 * 1000);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.info(`Creating verification code for ${provider}:${providerId}, userId: ${userId || 'null'}`);
|
logger.info(
|
||||||
|
`Creating verification code for ${provider}:${providerId}, userId: ${userId || 'null'}`
|
||||||
|
);
|
||||||
|
|
||||||
// Если userId не указан, добавляем запись без ссылки на пользователя
|
// Если userId не указан, добавляем запись без ссылки на пользователя
|
||||||
if (userId === null || userId === undefined) {
|
if (userId === null || userId === undefined) {
|
||||||
await db.query(
|
await db.query(
|
||||||
@@ -46,7 +51,7 @@ class VerificationService {
|
|||||||
error: error.message,
|
error: error.message,
|
||||||
provider,
|
provider,
|
||||||
providerId,
|
providerId,
|
||||||
userId
|
userId,
|
||||||
});
|
});
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -56,11 +61,11 @@ class VerificationService {
|
|||||||
async verifyCode(code, provider, providerId) {
|
async verifyCode(code, provider, providerId) {
|
||||||
try {
|
try {
|
||||||
logger.info(`Verifying code for ${provider}:${providerId}`);
|
logger.info(`Verifying code for ${provider}:${providerId}`);
|
||||||
|
|
||||||
// Преобразуем код в верхний регистр для сравнения
|
// Преобразуем код в верхний регистр для сравнения
|
||||||
const normalizedCode = code.toUpperCase();
|
const normalizedCode = code.toUpperCase();
|
||||||
logger.info(`Normalized code: ${normalizedCode}`);
|
logger.info(`Normalized code: ${normalizedCode}`);
|
||||||
|
|
||||||
// Проверим, есть ли такой код в базе (для отладки)
|
// Проверим, есть ли такой код в базе (для отладки)
|
||||||
const checkResult = await db.query(
|
const checkResult = await db.query(
|
||||||
`SELECT code FROM verification_codes
|
`SELECT code FROM verification_codes
|
||||||
@@ -70,13 +75,15 @@ class VerificationService {
|
|||||||
AND expires_at > NOW()`,
|
AND expires_at > NOW()`,
|
||||||
[provider, providerId]
|
[provider, providerId]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (checkResult.rows.length > 0) {
|
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 {
|
} else {
|
||||||
logger.warn(`No active codes found for ${provider}:${providerId}`);
|
logger.warn(`No active codes found for ${provider}:${providerId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await db.query(
|
const result = await db.query(
|
||||||
`SELECT * FROM verification_codes
|
`SELECT * FROM verification_codes
|
||||||
WHERE code = $1
|
WHERE code = $1
|
||||||
@@ -88,30 +95,29 @@ class VerificationService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (result.rows.length === 0) {
|
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: 'Неверный или истекший код' };
|
return { success: false, error: 'Неверный или истекший код' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const verification = result.rows[0];
|
const verification = result.rows[0];
|
||||||
|
|
||||||
// Отмечаем код как использованный
|
// Отмечаем код как использованный
|
||||||
await db.query(
|
await db.query('UPDATE verification_codes SET used = true WHERE id = $1', [verification.id]);
|
||||||
'UPDATE verification_codes SET used = true WHERE id = $1',
|
|
||||||
[verification.id]
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.info(`Code verified successfully for ${provider}:${providerId}`);
|
logger.info(`Code verified successfully for ${provider}:${providerId}`);
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
userId: verification.user_id,
|
userId: verification.user_id,
|
||||||
providerId: verification.provider_id
|
providerId: verification.provider_id,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error verifying code:', {
|
logger.error('Error verifying code:', {
|
||||||
error: error.message,
|
error: error.message,
|
||||||
code,
|
code,
|
||||||
provider,
|
provider,
|
||||||
providerId
|
providerId,
|
||||||
});
|
});
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -131,4 +137,4 @@ class VerificationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const verificationService = new VerificationService();
|
const verificationService = new VerificationService();
|
||||||
module.exports = verificationService;
|
module.exports = verificationService;
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
// Роли пользователей
|
// Роли пользователей
|
||||||
const USER_ROLES = {
|
const USER_ROLES = {
|
||||||
USER: 1,
|
USER: 1,
|
||||||
ADMIN: 2
|
ADMIN: 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Типы идентификаторов
|
// Типы идентификаторов
|
||||||
const IDENTITY_TYPES = {
|
const IDENTITY_TYPES = {
|
||||||
WALLET: 'wallet',
|
WALLET: 'wallet',
|
||||||
EMAIL: 'email',
|
EMAIL: 'email',
|
||||||
TELEGRAM: 'telegram'
|
TELEGRAM: 'telegram',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Каналы сообщений
|
// Каналы сообщений
|
||||||
const MESSAGE_CHANNELS = {
|
const MESSAGE_CHANNELS = {
|
||||||
WEB: 'web',
|
WEB: 'web',
|
||||||
TELEGRAM: 'telegram',
|
TELEGRAM: 'telegram',
|
||||||
EMAIL: 'email'
|
EMAIL: 'email',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Типы отправителей сообщений
|
// Типы отправителей сообщений
|
||||||
const SENDER_TYPES = {
|
const SENDER_TYPES = {
|
||||||
USER: 'user',
|
USER: 'user',
|
||||||
AI: 'ai',
|
AI: 'ai',
|
||||||
ADMIN: 'admin'
|
ADMIN: 'admin',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Коды ошибок
|
// Коды ошибок
|
||||||
@@ -31,20 +31,20 @@ const ERROR_CODES = {
|
|||||||
FORBIDDEN: 'forbidden',
|
FORBIDDEN: 'forbidden',
|
||||||
NOT_FOUND: 'not_found',
|
NOT_FOUND: 'not_found',
|
||||||
INTERNAL_ERROR: 'internal_error',
|
INTERNAL_ERROR: 'internal_error',
|
||||||
BAD_REQUEST: 'bad_request'
|
BAD_REQUEST: 'bad_request',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Настройки сессии
|
// Настройки сессии
|
||||||
const SESSION_CONFIG = {
|
const SESSION_CONFIG = {
|
||||||
COOKIE_MAX_AGE: 24 * 60 * 60 * 1000, // 24 часа
|
COOKIE_MAX_AGE: 24 * 60 * 60 * 1000, // 24 часа
|
||||||
COOKIE_SECURE: process.env.NODE_ENV === 'production',
|
COOKIE_SECURE: process.env.NODE_ENV === 'production',
|
||||||
COOKIE_SAME_SITE: 'lax'
|
COOKIE_SAME_SITE: 'lax',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Настройки API
|
// Настройки API
|
||||||
const API_CONFIG = {
|
const API_CONFIG = {
|
||||||
RATE_LIMIT: 100, // запросов в минуту
|
RATE_LIMIT: 100, // запросов в минуту
|
||||||
TIMEOUT: 30000 // 30 секунд
|
TIMEOUT: 30000, // 30 секунд
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@@ -54,5 +54,5 @@ module.exports = {
|
|||||||
SENDER_TYPES,
|
SENDER_TYPES,
|
||||||
ERROR_CODES,
|
ERROR_CODES,
|
||||||
SESSION_CONFIG,
|
SESSION_CONFIG,
|
||||||
API_CONFIG
|
API_CONFIG,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,4 +10,4 @@ function createError(message, statusCode = 500) {
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { createError };
|
module.exports = { createError };
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ async function addUserIdentity(userId, provider, providerId) {
|
|||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === '23505') { // Уникальное ограничение нарушено
|
if (error.code === '23505') {
|
||||||
|
// Уникальное ограничение нарушено
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
@@ -49,5 +50,5 @@ module.exports = {
|
|||||||
isValidEmail,
|
isValidEmail,
|
||||||
generateVerificationCode,
|
generateVerificationCode,
|
||||||
checkUserIdentity,
|
checkUserIdentity,
|
||||||
addUserIdentity
|
addUserIdentity,
|
||||||
};
|
};
|
||||||
|
|||||||
4936
frontend/package-lock.json
generated
Normal file
4936
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,8 @@
|
|||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --ignore-pattern 'node_modules/'",
|
"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: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": "prettier --write \"**/*.{js,vue,json,md}\"",
|
||||||
"format:check": "prettier --check \"**/*.{js,vue,json,md}\""
|
"format:check": "prettier --check \"**/*.{js,vue,json,md}\""
|
||||||
},
|
},
|
||||||
@@ -34,9 +36,13 @@
|
|||||||
"eslint-plugin-prettier": "^5.2.3",
|
"eslint-plugin-prettier": "^5.2.3",
|
||||||
"eslint-plugin-vue": "^9.32.0",
|
"eslint-plugin-vue": "^9.32.0",
|
||||||
"globals": "^16.0.0",
|
"globals": "^16.0.0",
|
||||||
|
"postcss-html": "^1.8.0",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"rollup": "^3.29.4",
|
"rollup": "^3.29.4",
|
||||||
"rollup-plugin-polyfill-node": "^0.12.0",
|
"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"
|
"vite": "^6.2.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -634,11 +634,17 @@
|
|||||||
const loadMessages = async (options = {}) => {
|
const loadMessages = async (options = {}) => {
|
||||||
const { silent = false, initial = false, authType = null } = 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;
|
if (messageLoading.value.isLoadingMore && !initial) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
messageLoading.value.isLoadingMore = true;
|
messageLoading.value.isLoadingMore = true;
|
||||||
messageLoading.value.isInProgress = true;
|
|
||||||
if (!silent) isLoading.value = true;
|
if (!silent) isLoading.value = true;
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
@@ -732,8 +738,8 @@
|
|||||||
removeFromStorage('guestId');
|
removeFromStorage('guestId');
|
||||||
}
|
}
|
||||||
} else if (response.data.messages && response.data.messages.length) {
|
} 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} сообщений из истории`);
|
console.log(`Загружено ${messages.value.length} сообщений из истории`);
|
||||||
|
|||||||
15
frontend/stylelint.config.cjs
Normal file
15
frontend/stylelint.config.cjs
Normal 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-]*$',
|
||||||
|
},
|
||||||
|
};
|
||||||
1672
frontend/yarn.lock
1672
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user