ваше сообщение коммита
This commit is contained in:
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();
|
||||
Reference in New Issue
Block a user