ваше сообщение коммита
This commit is contained in:
56
backend/db/migrations/015_users_table_refactor.sql
Normal file
56
backend/db/migrations/015_users_table_refactor.sql
Normal file
@@ -0,0 +1,56 @@
|
||||
-- Миграция для изменения структуры таблицы users
|
||||
-- Переносим данные из email и address в user_identities, затем преобразуем эти поля в first_name и last_name
|
||||
|
||||
-- Сначала проверяем, что все email и address уже существуют в user_identities
|
||||
DO $$
|
||||
BEGIN
|
||||
-- Переносим email в user_identities, если еще не перенесены
|
||||
INSERT INTO user_identities (user_id, provider, provider_id)
|
||||
SELECT id, 'email', email
|
||||
FROM users
|
||||
WHERE email IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM user_identities
|
||||
WHERE user_id = users.id AND provider = 'email' AND provider_id = users.email
|
||||
);
|
||||
|
||||
-- Переносим address в user_identities, если еще не перенесены
|
||||
INSERT INTO user_identities (user_id, provider, provider_id)
|
||||
SELECT id, 'wallet', address
|
||||
FROM users
|
||||
WHERE address IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM user_identities
|
||||
WHERE user_id = users.id AND provider = 'wallet' AND provider_id = users.address
|
||||
);
|
||||
|
||||
-- Логируем результаты миграции
|
||||
RAISE NOTICE 'Данные из колонок email и address перенесены в таблицу user_identities';
|
||||
END $$;
|
||||
|
||||
-- Теперь изменяем структуру таблицы users
|
||||
ALTER TABLE users
|
||||
DROP CONSTRAINT IF EXISTS users_email_key,
|
||||
DROP CONSTRAINT IF EXISTS users_address_key;
|
||||
|
||||
-- Добавляем временные колонки
|
||||
ALTER TABLE users
|
||||
ADD COLUMN first_name VARCHAR(255),
|
||||
ADD COLUMN last_name VARCHAR(255);
|
||||
|
||||
-- Убираем уникальность и переименовываем колонки email и address
|
||||
ALTER TABLE users
|
||||
ALTER COLUMN email DROP NOT NULL,
|
||||
ALTER COLUMN address DROP NOT NULL;
|
||||
|
||||
-- Удаляем колонки email и address
|
||||
ALTER TABLE users
|
||||
DROP COLUMN email,
|
||||
DROP COLUMN address;
|
||||
|
||||
-- Добавляем комментарии к столбцам
|
||||
COMMENT ON COLUMN users.first_name IS 'Имя пользователя';
|
||||
COMMENT ON COLUMN users.last_name IS 'Фамилия пользователя';
|
||||
|
||||
-- Обновляем статистику таблицы
|
||||
ANALYZE users;
|
||||
93
backend/db/migrations/016_fix_duplicate_identities.sql
Normal file
93
backend/db/migrations/016_fix_duplicate_identities.sql
Normal file
@@ -0,0 +1,93 @@
|
||||
-- Миграция для исправления дублирующихся записей в user_identities из-за разного регистра букв
|
||||
-- Исправляем записи для провайдеров wallet и email
|
||||
|
||||
-- Сначала удаляем существующее ограничение уникальности
|
||||
ALTER TABLE user_identities DROP CONSTRAINT IF EXISTS user_identities_provider_provider_id_key;
|
||||
|
||||
-- Создаем временную таблицу для хранения идентификаторов, которые нужно обработать
|
||||
CREATE TEMP TABLE duplicate_identities AS
|
||||
SELECT
|
||||
provider,
|
||||
LOWER(provider_id) as normalized_provider_id,
|
||||
array_agg(id) as id_list,
|
||||
array_agg(user_id) as user_id_list
|
||||
FROM user_identities
|
||||
WHERE provider IN ('wallet', 'email')
|
||||
GROUP BY provider, LOWER(provider_id)
|
||||
HAVING COUNT(*) > 1;
|
||||
|
||||
-- Логируем количество найденных дубликатов
|
||||
DO $$
|
||||
DECLARE
|
||||
duplicate_count INTEGER;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO duplicate_count FROM duplicate_identities;
|
||||
RAISE NOTICE 'Найдено % групп дублирующихся идентификаторов', duplicate_count;
|
||||
END $$;
|
||||
|
||||
-- Обновляем все записи, приводя provider_id к нижнему регистру
|
||||
UPDATE user_identities
|
||||
SET provider_id = LOWER(provider_id)
|
||||
WHERE provider IN ('wallet', 'email');
|
||||
|
||||
-- Удаляем дублирующиеся записи, оставляя только одну для каждой комбинации (provider, provider_id)
|
||||
WITH
|
||||
duplicates AS (
|
||||
SELECT
|
||||
id,
|
||||
provider,
|
||||
provider_id,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY provider, provider_id
|
||||
ORDER BY id
|
||||
) as row_num
|
||||
FROM user_identities
|
||||
WHERE provider IN ('wallet', 'email')
|
||||
)
|
||||
DELETE FROM user_identities
|
||||
WHERE id IN (
|
||||
SELECT id FROM duplicates WHERE row_num > 1
|
||||
);
|
||||
|
||||
-- Удаляем дублирующиеся записи для одного пользователя
|
||||
WITH
|
||||
user_duplicates AS (
|
||||
SELECT
|
||||
id,
|
||||
user_id,
|
||||
provider,
|
||||
provider_id,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY user_id, provider, provider_id
|
||||
ORDER BY id
|
||||
) as row_num
|
||||
FROM user_identities
|
||||
WHERE provider IN ('wallet', 'email')
|
||||
)
|
||||
DELETE FROM user_identities
|
||||
WHERE id IN (
|
||||
SELECT id FROM user_duplicates WHERE row_num > 1
|
||||
);
|
||||
|
||||
-- Добавляем обратно ограничение уникальности
|
||||
ALTER TABLE user_identities
|
||||
ADD CONSTRAINT user_identities_provider_provider_id_key
|
||||
UNIQUE (provider, provider_id);
|
||||
|
||||
-- Добавляем уникальный индекс для пользователей
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_indexes
|
||||
WHERE tablename = 'user_identities' AND indexname = 'unique_idx_user_identities_user_provider_provider_id'
|
||||
) THEN
|
||||
CREATE UNIQUE INDEX unique_idx_user_identities_user_provider_provider_id
|
||||
ON user_identities(user_id, provider, provider_id);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Логируем завершение миграции
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE 'Миграция для исправления дублирующихся идентификаторов завершена';
|
||||
END $$;
|
||||
40
backend/db/migrations/fix_duplicates_manual.sql
Normal file
40
backend/db/migrations/fix_duplicates_manual.sql
Normal file
@@ -0,0 +1,40 @@
|
||||
-- Скрипт для ручного исправления дублирующихся записей в базе данных
|
||||
|
||||
-- 1. Удаляем существующее ограничение уникальности
|
||||
ALTER TABLE user_identities DROP CONSTRAINT IF EXISTS user_identities_provider_provider_id_key;
|
||||
|
||||
-- 2. Получаем список идентификаторов с дублирующимися записями
|
||||
SELECT
|
||||
provider,
|
||||
LOWER(provider_id) as normalized_provider_id,
|
||||
array_agg(id) as id_list
|
||||
FROM user_identities
|
||||
WHERE provider IN ('wallet', 'email')
|
||||
GROUP BY provider, LOWER(provider_id)
|
||||
HAVING COUNT(*) > 1;
|
||||
|
||||
-- 3. Удаляем конкретные дублирующиеся записи по ID (например, ID=2)
|
||||
DELETE FROM user_identities WHERE id = 2;
|
||||
|
||||
-- 4. Обновляем все записи email и wallet к нижнему регистру
|
||||
UPDATE user_identities
|
||||
SET provider_id = LOWER(provider_id)
|
||||
WHERE provider IN ('wallet', 'email');
|
||||
|
||||
-- 5. Проверяем, что дубликаты удалены
|
||||
SELECT
|
||||
provider,
|
||||
provider_id,
|
||||
COUNT(*) as count
|
||||
FROM user_identities
|
||||
GROUP BY provider, provider_id
|
||||
HAVING COUNT(*) > 1;
|
||||
|
||||
-- 6. Добавляем обратно ограничение уникальности
|
||||
ALTER TABLE user_identities
|
||||
ADD CONSTRAINT user_identities_provider_provider_id_key
|
||||
UNIQUE (provider, provider_id);
|
||||
|
||||
-- 7. Создаем дополнительный индекс для (user_id, provider, provider_id)
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS unique_idx_user_identities_user_provider_provider_id
|
||||
ON user_identities(user_id, provider, provider_id);
|
||||
@@ -19,7 +19,8 @@
|
||||
"lint:fix": "eslint . --fix",
|
||||
"format": "prettier --write \"**/*.{js,vue,json,md}\"",
|
||||
"format:check": "prettier --check \"**/*.{js,vue,json,md}\"",
|
||||
"run-migrations": "node scripts/run-migrations.js"
|
||||
"run-migrations": "node scripts/run-migrations.js",
|
||||
"fix-duplicates": "node scripts/fix-duplicate-identities.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@langchain/community": "^0.3.34",
|
||||
|
||||
@@ -82,18 +82,49 @@ router.post('/verify', async (req, res) => {
|
||||
return res.status(401).json({ success: false, error: 'Invalid signature' });
|
||||
}
|
||||
|
||||
// Нормализуем адрес для использования в запросах
|
||||
const normalizedAddress = ethers.getAddress(address).toLowerCase();
|
||||
|
||||
// Проверяем nonce
|
||||
const nonceResult = await db.query('SELECT nonce FROM nonces WHERE identity_value = $1', [address.toLowerCase()]);
|
||||
const nonceResult = await db.query('SELECT nonce FROM nonces WHERE identity_value = $1', [normalizedAddress]);
|
||||
if (nonceResult.rows.length === 0 || nonceResult.rows[0].nonce !== message.match(/Nonce: ([^\n]+)/)[1]) {
|
||||
return res.status(401).json({ success: false, error: 'Invalid nonce' });
|
||||
}
|
||||
|
||||
// Находим или создаем пользователя
|
||||
const { userId, isAdmin } = await authService.findOrCreateUser(address.toLowerCase());
|
||||
let userId;
|
||||
let isAdmin = false;
|
||||
|
||||
// Сохраняем идентификаторы
|
||||
await identityService.saveIdentity(userId, 'wallet', address.toLowerCase(), true);
|
||||
// Проверяем, авторизован ли пользователь уже
|
||||
if (req.session.authenticated && req.session.userId) {
|
||||
// Если пользователь уже авторизован, привязываем кошелек к существующему пользователю
|
||||
userId = req.session.userId;
|
||||
logger.info(`[verify] Using existing authenticated user ${userId} for wallet ${normalizedAddress}`);
|
||||
|
||||
// Связываем кошелек с пользователем через identity-service для предотвращения дубликатов
|
||||
const linkResult = await authService.linkIdentity(
|
||||
userId,
|
||||
'wallet',
|
||||
address
|
||||
);
|
||||
|
||||
if (!linkResult.success && linkResult.error) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: linkResult.error
|
||||
});
|
||||
}
|
||||
|
||||
// Если linkResult.message содержит 'already exists', значит кошелек уже привязан
|
||||
logger.info(`[verify] Wallet ${normalizedAddress} linked to user ${userId}: ${linkResult.message || 'success'}`);
|
||||
} else {
|
||||
// Находим или создаем пользователя, если не авторизован
|
||||
const result = await authService.findOrCreateUser(address);
|
||||
userId = result.userId;
|
||||
isAdmin = result.isAdmin;
|
||||
logger.info(`[verify] Found or created user ${userId} for wallet ${normalizedAddress}`);
|
||||
}
|
||||
|
||||
// Сохраняем идентификаторы гостевой сессии
|
||||
if (guestId) {
|
||||
await identityService.saveIdentity(userId, 'guest', guestId, true);
|
||||
}
|
||||
@@ -103,10 +134,11 @@ router.post('/verify', async (req, res) => {
|
||||
}
|
||||
|
||||
// Проверяем наличие админских токенов
|
||||
const adminStatus = await authService.checkAdminTokens(address.toLowerCase());
|
||||
const adminStatus = await authService.checkAdminTokens(normalizedAddress);
|
||||
|
||||
if (adminStatus) {
|
||||
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
|
||||
isAdmin = true;
|
||||
}
|
||||
|
||||
// Обновляем сессию
|
||||
@@ -114,7 +146,7 @@ router.post('/verify', async (req, res) => {
|
||||
req.session.authenticated = true;
|
||||
req.session.authType = 'wallet';
|
||||
req.session.isAdmin = adminStatus || isAdmin;
|
||||
req.session.address = address.toLowerCase();
|
||||
req.session.address = normalizedAddress; // Всегда сохраняем нормализованный адрес
|
||||
|
||||
// Удаляем временный ID
|
||||
delete req.session.tempUserId;
|
||||
@@ -129,7 +161,7 @@ router.post('/verify', async (req, res) => {
|
||||
return res.json({
|
||||
success: true,
|
||||
userId,
|
||||
address,
|
||||
address: normalizedAddress, // Возвращаем нормализованный адрес
|
||||
isAdmin: adminStatus || isAdmin,
|
||||
authenticated: true
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ const router = express.Router();
|
||||
const { requireAuth } = require('../middleware/auth');
|
||||
const authService = require('../services/auth-service');
|
||||
const logger = require('../utils/logger');
|
||||
const db = require('../db');
|
||||
|
||||
// Получение всех идентификаторов пользователя
|
||||
router.get('/', requireAuth, async (req, res) => {
|
||||
@@ -22,7 +23,29 @@ router.post('/link', requireAuth, async (req, res) => {
|
||||
const { type, value } = req.body;
|
||||
const userId = req.session.userId;
|
||||
|
||||
await authService.linkIdentity(userId, type, value);
|
||||
// Если тип - wallet, сначала проверим, не привязан ли он уже к другому пользователю
|
||||
if (type === 'wallet') {
|
||||
const normalizedWallet = value.toLowerCase();
|
||||
|
||||
// Проверяем, существует ли уже такой кошелек
|
||||
const existingCheck = await db.query(
|
||||
`SELECT user_id FROM user_identities
|
||||
WHERE provider = 'wallet' AND provider_id = $1`,
|
||||
[normalizedWallet]
|
||||
);
|
||||
|
||||
if (existingCheck.rows.length > 0) {
|
||||
const existingUserId = existingCheck.rows[0].user_id;
|
||||
if (existingUserId !== userId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: `This wallet (${value}) is already linked to another account`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await authService.linkIdentity(userId, type, value);
|
||||
|
||||
// Обновляем сессию
|
||||
if (type === 'wallet') {
|
||||
@@ -41,6 +64,15 @@ router.post('/link', requireAuth, async (req, res) => {
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error linking identity:', error);
|
||||
|
||||
// Делаем более понятные сообщения об ошибках
|
||||
if (error.message && error.message.includes('already belongs to another user')) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: `This identity is already linked to another account`
|
||||
});
|
||||
}
|
||||
|
||||
res.status(500).json({ error: error.message || 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -43,4 +43,76 @@ router.post('/update-language', requireAuth, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Маршрут для обновления имени и фамилии пользователя
|
||||
router.post('/update-profile', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { firstName, lastName } = req.body;
|
||||
const userId = req.session.userId;
|
||||
|
||||
// Проверка валидности данных
|
||||
if (firstName && firstName.length > 255) {
|
||||
return res.status(400).json({ error: 'Имя слишком длинное (максимум 255 символов)' });
|
||||
}
|
||||
|
||||
if (lastName && lastName.length > 255) {
|
||||
return res.status(400).json({ error: 'Фамилия слишком длинная (максимум 255 символов)' });
|
||||
}
|
||||
|
||||
// Обновление имени и фамилии в базе данных
|
||||
await db.query(
|
||||
'UPDATE users SET first_name = $1, last_name = $2 WHERE id = $3',
|
||||
[firstName || null, lastName || null, userId]
|
||||
);
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
logger.error('Error updating user profile:', error);
|
||||
res.status(500).json({ error: 'Внутренняя ошибка сервера' });
|
||||
}
|
||||
});
|
||||
|
||||
// Маршрут для получения профиля пользователя
|
||||
router.get('/profile/current', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const userId = req.session.userId;
|
||||
|
||||
// Получение данных пользователя
|
||||
const userResult = await db.query(
|
||||
'SELECT id, username, first_name, last_name, role, status, created_at, preferred_language FROM users WHERE id = $1',
|
||||
[userId]
|
||||
);
|
||||
|
||||
if (userResult.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Пользователь не найден' });
|
||||
}
|
||||
|
||||
// Получение идентификаторов пользователя
|
||||
const identitiesResult = await db.query(
|
||||
'SELECT provider, provider_id FROM user_identities WHERE user_id = $1',
|
||||
[userId]
|
||||
);
|
||||
|
||||
const user = userResult.rows[0];
|
||||
const identities = identitiesResult.rows.reduce((acc, identity) => {
|
||||
acc[identity.provider] = identity.provider_id;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
res.json({
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
firstName: user.first_name,
|
||||
lastName: user.last_name,
|
||||
role: user.role,
|
||||
status: user.status,
|
||||
createdAt: user.created_at,
|
||||
preferredLanguage: user.preferred_language,
|
||||
identities
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error getting user profile:', error);
|
||||
res.status(500).json({ error: 'Внутренняя ошибка сервера' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
268
backend/scripts/fix-duplicate-identities.js
Normal file
268
backend/scripts/fix-duplicate-identities.js
Normal file
@@ -0,0 +1,268 @@
|
||||
/**
|
||||
* Скрипт для поиска и исправления дубликатов идентификаторов в базе данных
|
||||
*/
|
||||
require('dotenv').config();
|
||||
const { Pool } = require('pg');
|
||||
const { ethers } = require('ethers');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
// Настройка логирования
|
||||
const logDir = path.join(__dirname, '../logs');
|
||||
if (!fs.existsSync(logDir)) {
|
||||
fs.mkdirSync(logDir);
|
||||
}
|
||||
|
||||
const logFile = path.join(logDir, 'fix-duplicates.log');
|
||||
const logger = {
|
||||
log: message => {
|
||||
const timestamp = new Date().toISOString();
|
||||
const logMessage = `[${timestamp}] ${message}\n`;
|
||||
console.log(message);
|
||||
fs.appendFileSync(logFile, logMessage);
|
||||
},
|
||||
error: (message, error) => {
|
||||
const timestamp = new Date().toISOString();
|
||||
const errorDetail = error ? `: ${error.message}` : '';
|
||||
const logMessage = `[${timestamp}] ERROR: ${message}${errorDetail}\n`;
|
||||
console.error(`ERROR: ${message}${errorDetail}`);
|
||||
fs.appendFileSync(logFile, logMessage);
|
||||
}
|
||||
};
|
||||
|
||||
// Создаем подключение к базе данных
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
});
|
||||
|
||||
/**
|
||||
* Нормализует адрес кошелька
|
||||
* @param {string} address - Адрес кошелька
|
||||
* @returns {string} - Нормализованный адрес в нижнем регистре
|
||||
*/
|
||||
function normalizeWalletAddress(address) {
|
||||
try {
|
||||
return ethers.getAddress(address).toLowerCase();
|
||||
} catch (error) {
|
||||
logger.error(`Invalid wallet address: ${address}`, error);
|
||||
return address;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Находит все дубликаты идентификаторов кошельков
|
||||
*/
|
||||
async function findDuplicateWallets() {
|
||||
const client = await pool.connect();
|
||||
|
||||
try {
|
||||
logger.log('Поиск дубликатов wallet-идентификаторов...');
|
||||
|
||||
// Находим пары идентификаторов, которые отличаются только регистром
|
||||
const result = await client.query(`
|
||||
SELECT
|
||||
ui1.id as id1,
|
||||
ui1.user_id as user_id1,
|
||||
ui1.provider_id as provider_id1,
|
||||
ui2.id as id2,
|
||||
ui2.user_id as user_id2,
|
||||
ui2.provider_id as provider_id2
|
||||
FROM
|
||||
user_identities ui1
|
||||
JOIN
|
||||
user_identities ui2 ON ui1.id < ui2.id
|
||||
WHERE
|
||||
ui1.provider = 'wallet' AND
|
||||
ui2.provider = 'wallet' AND
|
||||
LOWER(ui1.provider_id) = LOWER(ui2.provider_id) AND
|
||||
ui1.provider_id <> ui2.provider_id
|
||||
`);
|
||||
|
||||
logger.log(`Найдено ${result.rows.length} потенциальных дубликатов wallet-идентификаторов`);
|
||||
|
||||
return result.rows;
|
||||
} catch (error) {
|
||||
logger.error('Ошибка при поиске дубликатов wallet-идентификаторов', error);
|
||||
throw error;
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Исправляет дубликаты идентификаторов
|
||||
* @param {Array} duplicates - Массив найденных дубликатов
|
||||
*/
|
||||
async function fixDuplicates(duplicates) {
|
||||
const client = await pool.connect();
|
||||
|
||||
try {
|
||||
logger.log('Исправление дубликатов идентификаторов...');
|
||||
|
||||
await client.query('BEGIN');
|
||||
|
||||
for (const dup of duplicates) {
|
||||
// Проверяем, принадлежат ли идентификаторы одному пользователю
|
||||
if (dup.user_id1 === dup.user_id2) {
|
||||
// Если да, удаляем один из дубликатов (не в нижнем регистре)
|
||||
const normalizedAddress = normalizeWalletAddress(dup.provider_id1);
|
||||
|
||||
// Определяем, какой идентификатор нужно удалить
|
||||
const idToDelete = dup.provider_id1 === normalizedAddress ? dup.id2 : dup.id1;
|
||||
|
||||
logger.log(`Удаление дубликата ID ${idToDelete} для адреса ${normalizedAddress}`);
|
||||
|
||||
await client.query('DELETE FROM user_identities WHERE id = $1', [idToDelete]);
|
||||
|
||||
// Проверяем, что второй идентификатор в нормализованной форме
|
||||
const remainingId = dup.provider_id1 === normalizedAddress ? dup.id1 : dup.id2;
|
||||
const remainingAddress = dup.provider_id1 === normalizedAddress ? dup.provider_id1 : dup.provider_id2;
|
||||
|
||||
if (remainingAddress !== normalizedAddress) {
|
||||
logger.log(`Обновление идентификатора ID ${remainingId} до нормализованного значения ${normalizedAddress}`);
|
||||
|
||||
await client.query(
|
||||
'UPDATE user_identities SET provider_id = $1 WHERE id = $2',
|
||||
[normalizedAddress, remainingId]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Если идентификаторы принадлежат разным пользователям, нужно решить конфликт
|
||||
// Для определения какой пользователь является основным, можно использовать:
|
||||
// 1. Количество сообщений/активности
|
||||
// 2. Дату создания аккаунта
|
||||
logger.log(`Конфликт: адрес ${dup.provider_id1}/${dup.provider_id2} привязан к разным пользователям: ${dup.user_id1} и ${dup.user_id2}`);
|
||||
|
||||
// Определяем, какой пользователь является основным
|
||||
const userInfoResult = await client.query(`
|
||||
SELECT
|
||||
id,
|
||||
(SELECT COUNT(*) FROM messages WHERE user_id = users.id) as message_count,
|
||||
(SELECT created_at FROM user_identities WHERE user_id = users.id ORDER BY created_at ASC LIMIT 1) as created_at
|
||||
FROM
|
||||
users
|
||||
WHERE
|
||||
id IN ($1, $2)
|
||||
ORDER BY
|
||||
message_count DESC, created_at ASC
|
||||
`, [dup.user_id1, dup.user_id2]);
|
||||
|
||||
// Если нет пользователей, пропускаем
|
||||
if (userInfoResult.rows.length === 0) {
|
||||
logger.log(`Пропуск: не найдены пользователи ${dup.user_id1} и ${dup.user_id2}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Выбираем первого пользователя как основного (с наибольшим количеством сообщений или самого старого)
|
||||
const mainUserId = userInfoResult.rows[0].id;
|
||||
const secondaryUserId = mainUserId === dup.user_id1 ? dup.user_id2 : dup.user_id1;
|
||||
|
||||
logger.log(`Объединение пользователей: сохраняем ID ${mainUserId}, удаляем ID ${secondaryUserId}`);
|
||||
|
||||
// Переносим все идентификаторы от вторичного пользователя к основному
|
||||
await client.query(`
|
||||
INSERT INTO user_identities (user_id, provider, provider_id)
|
||||
SELECT $1, provider, provider_id
|
||||
FROM user_identities
|
||||
WHERE user_id = $2
|
||||
ON CONFLICT DO NOTHING
|
||||
`, [mainUserId, secondaryUserId]);
|
||||
|
||||
// Переносим сообщения
|
||||
await client.query(`
|
||||
UPDATE messages
|
||||
SET user_id = $1
|
||||
WHERE user_id = $2
|
||||
`, [mainUserId, 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('COMMIT');
|
||||
logger.log('Исправление дубликатов успешно завершено');
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
logger.error('Ошибка при исправлении дубликатов', error);
|
||||
throw error;
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Основная функция
|
||||
*/
|
||||
async function main() {
|
||||
try {
|
||||
logger.log('Запуск скрипта исправления дубликатов идентификаторов...');
|
||||
|
||||
// Шаг 1: Нормализация всех адресов кошельков (приведение к нижнему регистру)
|
||||
const client = await pool.connect();
|
||||
|
||||
try {
|
||||
logger.log('Нормализация всех существующих адресов кошельков...');
|
||||
|
||||
await client.query('BEGIN');
|
||||
|
||||
// Получаем все идентификаторы кошельков
|
||||
const walletsResult = await client.query(`
|
||||
SELECT id, provider_id
|
||||
FROM user_identities
|
||||
WHERE provider = 'wallet'
|
||||
`);
|
||||
|
||||
logger.log(`Найдено ${walletsResult.rows.length} идентификаторов кошельков`);
|
||||
|
||||
// Обновляем каждый адрес к нормализованной форме
|
||||
let updatedCount = 0;
|
||||
|
||||
for (const wallet of walletsResult.rows) {
|
||||
try {
|
||||
const normalizedAddress = normalizeWalletAddress(wallet.provider_id);
|
||||
|
||||
if (normalizedAddress !== wallet.provider_id) {
|
||||
await client.query(
|
||||
'UPDATE user_identities SET provider_id = $1 WHERE id = $2',
|
||||
[normalizedAddress, wallet.id]
|
||||
);
|
||||
updatedCount++;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Ошибка при нормализации адреса ${wallet.provider_id}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
await client.query('COMMIT');
|
||||
logger.log(`Нормализовано ${updatedCount} адресов кошельков`);
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
logger.error('Ошибка при нормализации адресов кошельков', error);
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
|
||||
// Шаг 2: Поиск и исправление дубликатов
|
||||
const duplicates = await findDuplicateWallets();
|
||||
|
||||
if (duplicates.length > 0) {
|
||||
await fixDuplicates(duplicates);
|
||||
} else {
|
||||
logger.log('Дубликатов wallet-идентификаторов не найдено');
|
||||
}
|
||||
|
||||
logger.log('Скрипт успешно завершил работу');
|
||||
} catch (error) {
|
||||
logger.error('Критическая ошибка при выполнении скрипта', error);
|
||||
} finally {
|
||||
pool.end();
|
||||
}
|
||||
}
|
||||
|
||||
// Запускаем скрипт
|
||||
main();
|
||||
@@ -30,8 +30,15 @@ class AuthService {
|
||||
async verifySignature(message, signature, address) {
|
||||
try {
|
||||
if (!message || !signature || !address) return false;
|
||||
|
||||
// Нормализуем входящий адрес
|
||||
const normalizedAddress = ethers.getAddress(address).toLowerCase();
|
||||
|
||||
// Восстанавливаем адрес из подписи
|
||||
const recoveredAddress = ethers.verifyMessage(message, signature);
|
||||
return ethers.getAddress(recoveredAddress) === ethers.getAddress(address);
|
||||
|
||||
// Сравниваем нормализованные адреса
|
||||
return ethers.getAddress(recoveredAddress).toLowerCase() === normalizedAddress;
|
||||
} catch (error) {
|
||||
logger.error('Error in signature verification:', error);
|
||||
return false;
|
||||
@@ -45,15 +52,15 @@ class AuthService {
|
||||
*/
|
||||
async findOrCreateUser(address) {
|
||||
try {
|
||||
// Нормализуем адрес
|
||||
address = ethers.getAddress(address);
|
||||
// Нормализуем адрес - всегда приводим к нижнему регистру
|
||||
const normalizedAddress = ethers.getAddress(address).toLowerCase();
|
||||
|
||||
// Ищем пользователя по адресу в таблице user_identities
|
||||
const userResult = await db.query(`
|
||||
SELECT u.* FROM users u
|
||||
JOIN user_identities ui ON u.id = ui.user_id
|
||||
WHERE ui.provider = 'wallet' AND ui.provider_id = $1
|
||||
`, [address]);
|
||||
`, [normalizedAddress]);
|
||||
|
||||
if (userResult.rows.length > 0) {
|
||||
const user = userResult.rows[0];
|
||||
@@ -71,13 +78,22 @@ class AuthService {
|
||||
|
||||
const userId = newUserResult.rows[0].id;
|
||||
|
||||
// Добавляем идентификатор кошелька
|
||||
// Добавляем идентификатор кошелька (всегда в нижнем регистре)
|
||||
await db.query(
|
||||
'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)',
|
||||
[userId, 'wallet', address]
|
||||
[userId, 'wallet', normalizedAddress]
|
||||
);
|
||||
|
||||
return { userId, isAdmin: false };
|
||||
// Проверяем, есть ли у пользователя роль админа
|
||||
const isAdmin = await this.checkAdminRole(normalizedAddress);
|
||||
|
||||
// Если у пользователя есть админские токены, обновляем его роль
|
||||
if (isAdmin) {
|
||||
await db.query('UPDATE users SET role = $1 WHERE id = $2', ['admin', userId]);
|
||||
logger.info(`New user ${userId} with wallet ${normalizedAddress} automatically granted admin role`);
|
||||
}
|
||||
|
||||
return { userId, isAdmin };
|
||||
} catch (error) {
|
||||
console.error('Error finding or creating user:', error);
|
||||
throw error;
|
||||
@@ -454,8 +470,8 @@ class AuthService {
|
||||
// Если есть гостевой ID в сессии, сохраняем его для нового пользователя
|
||||
if (session.guestId && isNewUser) {
|
||||
await db.query(
|
||||
'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING',
|
||||
[userId, 'guest', session.guestId]
|
||||
'INSERT INTO guest_user_mapping (user_id, guest_id) VALUES ($1, $2) ON CONFLICT (guest_id) DO UPDATE SET user_id = $1',
|
||||
[userId, session.guestId]
|
||||
);
|
||||
logger.info(`[verifyTelegramAuth] Saved guest ID ${session.guestId} for user ${userId}`);
|
||||
}
|
||||
@@ -597,18 +613,25 @@ class AuthService {
|
||||
}
|
||||
|
||||
// Нормализуем значение идентификатора
|
||||
if (provider === 'wallet' && providerId) {
|
||||
providerId = providerId.toLowerCase();
|
||||
} else if (provider === 'email' && providerId) {
|
||||
providerId = providerId.toLowerCase();
|
||||
let normalizedProviderId = providerId;
|
||||
if (provider === 'wallet') {
|
||||
// Для кошельков используем ethers для валидации и нормализации
|
||||
try {
|
||||
normalizedProviderId = ethers.getAddress(providerId).toLowerCase();
|
||||
} catch (error) {
|
||||
logger.error(`[AuthService] Invalid wallet address: ${providerId}`, error);
|
||||
throw new Error('Invalid wallet address');
|
||||
}
|
||||
} else if (provider === 'email') {
|
||||
normalizedProviderId = providerId.toLowerCase();
|
||||
}
|
||||
|
||||
logger.info(`[AuthService] Linking identity ${provider}:${providerId} to user ${userId}`);
|
||||
logger.info(`[AuthService] Linking identity ${provider}:${normalizedProviderId} to user ${userId}`);
|
||||
|
||||
// Проверяем, существует ли уже такой идентификатор
|
||||
const existingResult = await db.query(
|
||||
`SELECT user_id FROM user_identities WHERE provider = $1 AND provider_id = $2`,
|
||||
[provider, providerId]
|
||||
[provider, normalizedProviderId]
|
||||
);
|
||||
|
||||
if (existingResult.rows.length > 0) {
|
||||
@@ -616,11 +639,11 @@ class AuthService {
|
||||
|
||||
// Если идентификатор уже принадлежит этому пользователю, ничего не делаем
|
||||
if (existingUserId === userId) {
|
||||
logger.info(`[AuthService] Identity ${provider}:${providerId} already exists for user ${userId}`);
|
||||
logger.info(`[AuthService] Identity ${provider}:${normalizedProviderId} already exists for user ${userId}`);
|
||||
return { success: true, message: 'Identity already exists' };
|
||||
} else {
|
||||
// Если идентификатор принадлежит другому пользователю, возвращаем ошибку
|
||||
logger.warn(`[AuthService] Identity ${provider}:${providerId} 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})`);
|
||||
}
|
||||
}
|
||||
@@ -629,13 +652,13 @@ class AuthService {
|
||||
await db.query(
|
||||
`INSERT INTO user_identities (user_id, provider, provider_id)
|
||||
VALUES ($1, $2, $3)`,
|
||||
[userId, provider, providerId]
|
||||
[userId, provider, normalizedProviderId]
|
||||
);
|
||||
|
||||
// Проверяем и обновляем роль администратора, если это идентификатор кошелька
|
||||
let isAdmin = false;
|
||||
if (provider === 'wallet') {
|
||||
isAdmin = await this.checkAdminTokens(providerId);
|
||||
isAdmin = await this.checkAdminTokens(normalizedProviderId);
|
||||
|
||||
// Обновляем роль пользователя в базе данных, если нужно
|
||||
if (isAdmin) {
|
||||
@@ -647,7 +670,7 @@ class AuthService {
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`[AuthService] Identity ${provider}:${providerId} successfully linked to user ${userId}`);
|
||||
logger.info(`[AuthService] Identity ${provider}:${normalizedProviderId} successfully linked to user ${userId}`);
|
||||
return { success: true, isAdmin };
|
||||
} catch (error) {
|
||||
logger.error(`[AuthService] Error linking identity ${provider}:${providerId} to user ${userId}:`, error);
|
||||
|
||||
@@ -5,6 +5,32 @@ const logger = require('../utils/logger');
|
||||
* Сервис для работы с идентификаторами пользователей
|
||||
*/
|
||||
class IdentityService {
|
||||
/**
|
||||
* Нормализует значения идентификаторов (приводит к нижнему регистру где нужно)
|
||||
* @param {string} provider - Тип идентификатора
|
||||
* @param {string} providerId - Значение идентификатора
|
||||
* @returns {object} - Нормализованные значения
|
||||
*/
|
||||
normalizeIdentity(provider, providerId) {
|
||||
if (!provider || !providerId) {
|
||||
return { provider, providerId };
|
||||
}
|
||||
|
||||
// Приводим провайдер к нижнему регистру
|
||||
const normalizedProvider = provider.toLowerCase();
|
||||
|
||||
// Для email и wallet приводим значение к нижнему регистру
|
||||
let normalizedProviderId = providerId;
|
||||
if (normalizedProvider === 'wallet' || normalizedProvider === 'email') {
|
||||
normalizedProviderId = providerId.toLowerCase();
|
||||
}
|
||||
|
||||
return {
|
||||
provider: normalizedProvider,
|
||||
providerId: normalizedProviderId
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохраняет идентификатор пользователя в базу данных
|
||||
* @param {number} userId - ID пользователя
|
||||
@@ -23,20 +49,18 @@ class IdentityService {
|
||||
};
|
||||
}
|
||||
|
||||
// Приводим provider и providerId к нужному формату
|
||||
provider = provider.toLowerCase();
|
||||
if (provider === 'wallet' || provider === 'email') {
|
||||
providerId = providerId.toLowerCase();
|
||||
}
|
||||
// Нормализуем значения
|
||||
const { provider: normalizedProvider, providerId: normalizedProviderId } =
|
||||
this.normalizeIdentity(provider, providerId);
|
||||
|
||||
// Проверяем тип провайдера и перенаправляем гостевые идентификаторы в guest_user_mapping
|
||||
if (provider === 'guest') {
|
||||
logger.info(`[IdentityService] Converting guest identity for user ${userId} to guest_user_mapping: ${providerId}`);
|
||||
if (normalizedProvider === 'guest') {
|
||||
logger.info(`[IdentityService] Converting guest identity for user ${userId} to guest_user_mapping: ${normalizedProviderId}`);
|
||||
|
||||
try {
|
||||
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',
|
||||
[userId, providerId]
|
||||
[userId, normalizedProviderId]
|
||||
);
|
||||
return { success: true };
|
||||
} catch (guestError) {
|
||||
@@ -47,20 +71,20 @@ class IdentityService {
|
||||
|
||||
// Проверяем, разрешен ли такой тип провайдера
|
||||
const allowedProviders = ['email', 'wallet', 'telegram', 'username'];
|
||||
if (!allowedProviders.includes(provider)) {
|
||||
logger.warn(`[IdentityService] Invalid provider type: ${provider}`);
|
||||
if (!allowedProviders.includes(normalizedProvider)) {
|
||||
logger.warn(`[IdentityService] Invalid provider type: ${normalizedProvider}`);
|
||||
return {
|
||||
success: false,
|
||||
error: `Invalid provider type. Allowed types: ${allowedProviders.join(', ')}`
|
||||
};
|
||||
}
|
||||
|
||||
logger.info(`[IdentityService] Saving identity for user ${userId}: ${provider}:${providerId}`);
|
||||
logger.info(`[IdentityService] Saving identity for user ${userId}: ${normalizedProvider}:${normalizedProviderId}`);
|
||||
|
||||
// Проверяем, существует ли уже такой идентификатор
|
||||
const existingResult = await db.query(
|
||||
`SELECT user_id FROM user_identities WHERE provider = $1 AND provider_id = $2`,
|
||||
[provider, providerId]
|
||||
[normalizedProvider, normalizedProviderId]
|
||||
);
|
||||
|
||||
if (existingResult.rows.length > 0) {
|
||||
@@ -68,10 +92,10 @@ class IdentityService {
|
||||
|
||||
// Если идентификатор уже принадлежит этому пользователю, ничего не делаем
|
||||
if (existingUserId === userId) {
|
||||
logger.info(`[IdentityService] Identity ${provider}:${providerId} already exists for user ${userId}`);
|
||||
logger.info(`[IdentityService] Identity ${normalizedProvider}:${normalizedProviderId} already exists for user ${userId}`);
|
||||
} else {
|
||||
// Если идентификатор принадлежит другому пользователю, логируем это
|
||||
logger.warn(`[IdentityService] Identity ${provider}:${providerId} already belongs to user ${existingUserId}, not user ${userId}`);
|
||||
logger.warn(`[IdentityService] Identity ${normalizedProvider}:${normalizedProviderId} already belongs to user ${existingUserId}, not user ${userId}`);
|
||||
return {
|
||||
success: false,
|
||||
error: `Identity already belongs to another user (${existingUserId})`
|
||||
@@ -82,9 +106,9 @@ class IdentityService {
|
||||
await db.query(
|
||||
`INSERT INTO user_identities (user_id, provider, provider_id)
|
||||
VALUES ($1, $2, $3)`,
|
||||
[userId, provider, providerId]
|
||||
[userId, normalizedProvider, normalizedProviderId]
|
||||
);
|
||||
logger.info(`[IdentityService] Created new identity ${provider}:${providerId} for user ${userId}`);
|
||||
logger.info(`[IdentityService] Created new identity ${normalizedProvider}:${normalizedProviderId} for user ${userId}`);
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
@@ -158,19 +182,23 @@ class IdentityService {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Нормализуем значения
|
||||
const { provider: normalizedProvider, providerId: normalizedProviderId } =
|
||||
this.normalizeIdentity(provider, providerId);
|
||||
|
||||
const result = await db.query(
|
||||
`SELECT u.id, u.role FROM users u
|
||||
JOIN user_identities ui ON u.id = ui.user_id
|
||||
WHERE ui.provider = $1 AND ui.provider_id = $2`,
|
||||
[provider, providerId]
|
||||
[normalizedProvider, normalizedProviderId]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
logger.info(`[IdentityService] No user found with identity ${provider}:${providerId}`);
|
||||
logger.info(`[IdentityService] No user found with identity ${normalizedProvider}:${normalizedProviderId}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
logger.info(`[IdentityService] Found user ${result.rows[0].id} with identity ${provider}:${providerId}`);
|
||||
logger.info(`[IdentityService] Found user ${result.rows[0].id} with identity ${normalizedProvider}:${normalizedProviderId}`);
|
||||
return result.rows[0];
|
||||
} catch (error) {
|
||||
logger.error(`[IdentityService] Error finding user by identity ${provider}:${providerId}:`, error);
|
||||
@@ -195,12 +223,12 @@ class IdentityService {
|
||||
|
||||
// Сохраняем все постоянные идентификаторы из сессии
|
||||
if (session.email) {
|
||||
const emailResult = await this.saveIdentity(userId, 'email', session.email.toLowerCase(), true);
|
||||
const emailResult = await this.saveIdentity(userId, 'email', session.email, true);
|
||||
results.push({ type: 'email', result: emailResult });
|
||||
}
|
||||
|
||||
if (session.address) {
|
||||
const walletResult = await this.saveIdentity(userId, 'wallet', session.address.toLowerCase(), true);
|
||||
const walletResult = await this.saveIdentity(userId, 'wallet', session.address, true);
|
||||
results.push({ type: 'wallet', result: walletResult });
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ async function getBot() {
|
||||
|
||||
const verification = codeResult.rows[0];
|
||||
const providerId = verification.provider_id;
|
||||
const linkedUserId = verification.user_id; // Получаем связанный userId если он есть
|
||||
let userId;
|
||||
|
||||
// Отмечаем код как использованный
|
||||
@@ -62,33 +63,72 @@ async function getBot() {
|
||||
userId = existingTelegramUser.rows[0].user_id;
|
||||
logger.info(`Using existing user ${userId} for Telegram account ${ctx.from.id}`);
|
||||
} else {
|
||||
// Создаем нового пользователя, если нет существующего с этим Telegram ID
|
||||
const userResult = await db.query(
|
||||
'INSERT INTO users (created_at, role) VALUES (NOW(), $1) RETURNING id',
|
||||
['user']
|
||||
);
|
||||
userId = userResult.rows[0].id;
|
||||
|
||||
// Связываем Telegram с новым пользователем
|
||||
await db.query(
|
||||
`INSERT INTO user_identities
|
||||
(user_id, provider, provider_id, created_at)
|
||||
VALUES ($1, $2, $3, NOW())`,
|
||||
[userId, 'telegram', ctx.from.id.toString()]
|
||||
);
|
||||
|
||||
// Если был гостевой ID, связываем его с новым пользователем
|
||||
if (providerId) {
|
||||
// Если код верификации был связан с существующим пользователем, используем его
|
||||
if (linkedUserId) {
|
||||
// Используем userId из кода верификации
|
||||
userId = linkedUserId;
|
||||
// Связываем Telegram с этим пользователем
|
||||
await db.query(
|
||||
`INSERT INTO user_identities
|
||||
(user_id, provider, provider_id, created_at)
|
||||
VALUES ($1, $2, $3, NOW())
|
||||
ON CONFLICT (provider, provider_id) DO NOTHING`,
|
||||
[userId, 'guest', providerId]
|
||||
VALUES ($1, $2, $3, NOW())`,
|
||||
[userId, 'telegram', ctx.from.id.toString()]
|
||||
);
|
||||
logger.info(`Linked Telegram account ${ctx.from.id} to pre-authenticated user ${userId}`);
|
||||
} else {
|
||||
// Проверяем, есть ли пользователь, связанный с гостевым идентификатором
|
||||
let existingUserWithGuestId = null;
|
||||
if (providerId) {
|
||||
const guestUserResult = await db.query(
|
||||
`SELECT user_id FROM guest_user_mapping WHERE guest_id = $1`,
|
||||
[providerId]
|
||||
);
|
||||
if (guestUserResult.rows.length > 0) {
|
||||
existingUserWithGuestId = guestUserResult.rows[0].user_id;
|
||||
logger.info(`Found existing user ${existingUserWithGuestId} by guest ID ${providerId}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (existingUserWithGuestId) {
|
||||
// Используем существующего пользователя и добавляем ему Telegram идентификатор
|
||||
userId = existingUserWithGuestId;
|
||||
await db.query(
|
||||
`INSERT INTO user_identities
|
||||
(user_id, provider, provider_id, created_at)
|
||||
VALUES ($1, $2, $3, NOW())`,
|
||||
[userId, 'telegram', ctx.from.id.toString()]
|
||||
);
|
||||
logger.info(`Linked Telegram account ${ctx.from.id} to existing user ${userId}`);
|
||||
} else {
|
||||
// Создаем нового пользователя, если не нашли существующего
|
||||
const userResult = await db.query(
|
||||
'INSERT INTO users (created_at, role) VALUES (NOW(), $1) RETURNING id',
|
||||
['user']
|
||||
);
|
||||
userId = userResult.rows[0].id;
|
||||
|
||||
// Связываем Telegram с новым пользователем
|
||||
await db.query(
|
||||
`INSERT INTO user_identities
|
||||
(user_id, provider, provider_id, created_at)
|
||||
VALUES ($1, $2, $3, NOW())`,
|
||||
[userId, 'telegram', ctx.from.id.toString()]
|
||||
);
|
||||
|
||||
// Если был гостевой ID, связываем его с новым пользователем
|
||||
if (providerId) {
|
||||
await db.query(
|
||||
`INSERT INTO guest_user_mapping
|
||||
(user_id, guest_id)
|
||||
VALUES ($1, $2)
|
||||
ON CONFLICT (guest_id) DO UPDATE SET user_id = $1`,
|
||||
[userId, providerId]
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`Created new user ${userId} with Telegram account ${ctx.from.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`Created new user ${userId} with Telegram account ${ctx.from.id}`);
|
||||
}
|
||||
|
||||
// Обновляем сессию в базе данных
|
||||
@@ -151,14 +191,30 @@ async function initTelegramAuth(session) {
|
||||
// Реальный пользователь будет создан или найден при проверке кода через бота
|
||||
const tempId = crypto.randomBytes(16).toString('hex');
|
||||
|
||||
// Создаем код через сервис верификации с временным идентификатором
|
||||
// Если пользователь уже авторизован, сохраняем его userId в guest_user_mapping
|
||||
// чтобы потом при авторизации через бота этот пользователь был найден
|
||||
if (session && session.authenticated && session.userId) {
|
||||
const guestId = session.guestId || tempId;
|
||||
|
||||
// Связываем гостевой ID с текущим пользователем
|
||||
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`,
|
||||
[session.userId, guestId]
|
||||
);
|
||||
|
||||
logger.info(`[initTelegramAuth] Linked guestId ${guestId} to authenticated user ${session.userId}`);
|
||||
}
|
||||
|
||||
// Создаем код через сервис верификации с идентификатором
|
||||
const code = await verificationService.createVerificationCode(
|
||||
'telegram',
|
||||
session.guestId || tempId,
|
||||
null // Не привязываем к конкретному userId на этом этапе
|
||||
session.authenticated ? session.userId : null
|
||||
);
|
||||
|
||||
logger.info(`[initTelegramAuth] Created verification code for guestId: ${session.guestId || tempId}`);
|
||||
logger.info(`[initTelegramAuth] Created verification code for guestId: ${session.guestId || tempId}${session.authenticated ? `, userId: ${session.userId}` : ''}`);
|
||||
|
||||
return {
|
||||
verificationCode: code,
|
||||
|
||||
Reference in New Issue
Block a user