Implemented request tracking and cancellation to prevent stale API responses from overwriting current beneficiary data. Changes: - Added loadingBeneficiaryIdRef to track which beneficiary is being loaded - Added AbortController to cancel in-flight requests - Validate beneficiary ID before applying state updates - Cleanup on component unmount to prevent memory leaks This fixes the issue where rapidly switching between beneficiaries would show wrong data if slower requests completed after faster ones. 🤖 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
305 lines
9.4 KiB
JavaScript
305 lines
9.4 KiB
JavaScript
/**
|
||
* Скрипт для массового создания Legacy Deployments
|
||
*
|
||
* Что делает:
|
||
* 1. Получает всех beneficiaries из WellNuo DB
|
||
* 2. Для каждого без валидного legacy_deployment_id:
|
||
* - Вызывает set_deployment в Legacy API
|
||
* - Получает deployment_id из ответа
|
||
* - Сохраняет в beneficiary_deployments.legacy_deployment_id
|
||
*/
|
||
|
||
const https = require('https');
|
||
const { Client } = require('pg');
|
||
require('dotenv').config();
|
||
|
||
// Legacy API credentials
|
||
const LEGACY_API = {
|
||
host: 'eluxnetworks.net',
|
||
path: '/function/well-api/api',
|
||
user: process.env.LEGACY_API_USERNAME || 'robster',
|
||
password: process.env.LEGACY_API_PASSWORD || 'rob2'
|
||
};
|
||
|
||
// WellNuo DB credentials
|
||
const DB_CONFIG = {
|
||
host: process.env.DB_HOST,
|
||
port: parseInt(process.env.DB_PORT || '5432'),
|
||
database: process.env.DB_NAME,
|
||
user: process.env.DB_USER,
|
||
password: process.env.DB_PASSWORD,
|
||
connectionTimeoutMillis: 15000,
|
||
ssl: { rejectUnauthorized: false }
|
||
};
|
||
|
||
// 1x1 pixel JPEG (minimal photo for API)
|
||
const MINI_PHOTO = '/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAn/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCwAB//2Q==';
|
||
|
||
// Helper: Login to Legacy API and get token
|
||
async function legacyLogin() {
|
||
return new Promise((resolve, reject) => {
|
||
const querystring = require('querystring');
|
||
const data = querystring.stringify({
|
||
function: 'credentials',
|
||
user_name: LEGACY_API.user,
|
||
ps: LEGACY_API.password,
|
||
clientId: '001',
|
||
nonce: Date.now().toString()
|
||
});
|
||
|
||
const options = {
|
||
hostname: LEGACY_API.host,
|
||
path: LEGACY_API.path,
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/x-www-form-urlencoded',
|
||
'Content-Length': Buffer.byteLength(data)
|
||
}
|
||
};
|
||
|
||
const req = https.request(options, (res) => {
|
||
let body = '';
|
||
res.on('data', (chunk) => body += chunk);
|
||
res.on('end', () => {
|
||
try {
|
||
const result = JSON.parse(body);
|
||
if (result.access_token) {
|
||
resolve(result.access_token);
|
||
} else {
|
||
reject(new Error('No token in login response: ' + body));
|
||
}
|
||
} catch (e) {
|
||
reject(new Error('Invalid JSON: ' + body.substring(0, 200)));
|
||
}
|
||
});
|
||
});
|
||
|
||
req.on('error', reject);
|
||
req.write(data);
|
||
req.end();
|
||
});
|
||
}
|
||
|
||
// Helper: make Legacy API request with token
|
||
function legacyRequest(token, params) {
|
||
return new Promise((resolve, reject) => {
|
||
const querystring = require('querystring');
|
||
const data = querystring.stringify({
|
||
user_name: LEGACY_API.user,
|
||
token: token,
|
||
...params
|
||
});
|
||
|
||
const options = {
|
||
hostname: LEGACY_API.host,
|
||
path: LEGACY_API.path,
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/x-www-form-urlencoded',
|
||
'Content-Length': Buffer.byteLength(data)
|
||
}
|
||
};
|
||
|
||
const req = https.request(options, (res) => {
|
||
let body = '';
|
||
res.on('data', (chunk) => body += chunk);
|
||
res.on('end', () => {
|
||
try {
|
||
resolve(JSON.parse(body));
|
||
} catch (e) {
|
||
resolve({ error: 'Invalid JSON', raw: body.substring(0, 200) });
|
||
}
|
||
});
|
||
});
|
||
|
||
req.on('error', reject);
|
||
req.write(data);
|
||
req.end();
|
||
});
|
||
}
|
||
|
||
// Create deployment in Legacy API
|
||
async function createLegacyDeployment(token, beneficiaryId, beneficiaryName) {
|
||
// Format name for Legacy API (needs exactly 2 words)
|
||
const nameParts = (beneficiaryName || 'User').trim().split(/\s+/);
|
||
let firstName, lastName;
|
||
if (nameParts.length === 1) {
|
||
firstName = nameParts[0];
|
||
lastName = 'User';
|
||
} else {
|
||
firstName = nameParts[0];
|
||
lastName = nameParts.slice(1).join(' ').substring(0, 20);
|
||
}
|
||
|
||
const timestamp = Date.now();
|
||
const beneficiaryUsername = 'b' + beneficiaryId + '_' + timestamp;
|
||
const password = Math.random().toString(36).substring(2, 15);
|
||
const uniqueEmail = 'b' + beneficiaryId + '_' + timestamp + '@wellnuo.app';
|
||
|
||
const result = await legacyRequest(token, {
|
||
function: 'set_deployment',
|
||
deployment: 'NEW',
|
||
beneficiary_name: firstName + ' ' + lastName,
|
||
beneficiary_email: uniqueEmail,
|
||
beneficiary_user_name: beneficiaryUsername,
|
||
beneficiary_password: password,
|
||
beneficiary_address: 'test',
|
||
beneficiary_photo: MINI_PHOTO,
|
||
firstName: firstName,
|
||
lastName: lastName,
|
||
first_name: firstName,
|
||
last_name: lastName,
|
||
new_user_name: beneficiaryUsername,
|
||
phone_number: '+10000000000',
|
||
key: password,
|
||
signature: 'WellNuo',
|
||
gps_age: '0',
|
||
wifis: '[]',
|
||
devices: '[]'
|
||
});
|
||
|
||
return result;
|
||
}
|
||
|
||
async function main() {
|
||
console.log('='.repeat(70));
|
||
console.log('СОЗДАНИЕ LEGACY DEPLOYMENTS ДЛЯ ВСЕХ BENEFICIARIES');
|
||
console.log('='.repeat(70));
|
||
console.log();
|
||
|
||
// 1. Login to Legacy API
|
||
console.log('1. Авторизация в Legacy API...');
|
||
let token;
|
||
try {
|
||
token = await legacyLogin();
|
||
console.log(' ✅ Получен токен');
|
||
} catch (error) {
|
||
console.error(' ❌ ОШИБКА:', error.message);
|
||
process.exit(1);
|
||
}
|
||
console.log();
|
||
|
||
// 2. Get existing deployments from Legacy API
|
||
console.log('2. Получаем существующие deployments из Legacy API...');
|
||
const existingDeployments = await legacyRequest(token, {
|
||
function: 'deployments_list',
|
||
first: '0',
|
||
last: '500'
|
||
});
|
||
|
||
const existingIds = new Set();
|
||
if (existingDeployments.result_list) {
|
||
existingDeployments.result_list.forEach(d => existingIds.add(d.deployment_id));
|
||
console.log(' Существующие IDs: ' + [...existingIds].sort((a,b) => a-b).join(', '));
|
||
}
|
||
console.log();
|
||
|
||
// 3. Connect to WellNuo DB
|
||
console.log('3. Подключаемся к WellNuo DB...');
|
||
const client = new Client(DB_CONFIG);
|
||
|
||
try {
|
||
await client.connect();
|
||
console.log(' ✅ Подключено');
|
||
console.log();
|
||
|
||
// Get all beneficiaries with their deployments
|
||
const result = await client.query(`
|
||
SELECT
|
||
b.id as beneficiary_id,
|
||
b.name as beneficiary_name,
|
||
bd.id as deployment_id,
|
||
bd.legacy_deployment_id
|
||
FROM beneficiaries b
|
||
LEFT JOIN beneficiary_deployments bd ON b.id = bd.beneficiary_id
|
||
ORDER BY b.id
|
||
`);
|
||
|
||
console.log('4. Найдено ' + result.rows.length + ' записей в БД');
|
||
console.log();
|
||
|
||
// Filter: need to create deployment for those with NULL or invalid legacy_deployment_id
|
||
const needsCreation = result.rows.filter(row => {
|
||
if (row.legacy_deployment_id === null) return true;
|
||
if (!existingIds.has(row.legacy_deployment_id)) return true;
|
||
return false;
|
||
});
|
||
|
||
console.log('5. Нужно создать deployments для ' + needsCreation.length + ' beneficiaries:');
|
||
console.log('-'.repeat(70));
|
||
|
||
const created = [];
|
||
const failed = [];
|
||
|
||
for (const row of needsCreation) {
|
||
const name = (row.beneficiary_name || 'Unknown').substring(0, 20);
|
||
process.stdout.write(' #' + row.beneficiary_id + ' (' + name + ')... ');
|
||
|
||
try {
|
||
const response = await createLegacyDeployment(token, row.beneficiary_id, row.beneficiary_name);
|
||
|
||
if (response.deployment_id && response.deployment_id > 0) {
|
||
console.log('✅ deployment_id=' + response.deployment_id);
|
||
|
||
// Update database
|
||
if (row.deployment_id) {
|
||
await client.query(
|
||
'UPDATE beneficiary_deployments SET legacy_deployment_id = $1 WHERE id = $2',
|
||
[response.deployment_id, row.deployment_id]
|
||
);
|
||
}
|
||
|
||
created.push({
|
||
beneficiaryId: row.beneficiary_id,
|
||
deploymentId: row.deployment_id,
|
||
legacyDeploymentId: response.deployment_id
|
||
});
|
||
} else {
|
||
console.log('❌ No deployment_id: ' + JSON.stringify(response).substring(0, 100));
|
||
failed.push({ beneficiaryId: row.beneficiary_id, error: 'No deployment_id' });
|
||
}
|
||
|
||
// Small delay to not overwhelm API
|
||
await new Promise(r => setTimeout(r, 300));
|
||
|
||
} catch (error) {
|
||
console.log('❌ Error: ' + error.message);
|
||
failed.push({ beneficiaryId: row.beneficiary_id, error: error.message });
|
||
}
|
||
}
|
||
|
||
console.log();
|
||
console.log('='.repeat(70));
|
||
console.log('РЕЗУЛЬТАТЫ:');
|
||
console.log(' ✅ Создано: ' + created.length);
|
||
console.log(' ❌ Ошибок: ' + failed.length);
|
||
console.log();
|
||
|
||
if (created.length > 0) {
|
||
console.log('Созданные deployments:');
|
||
for (const c of created) {
|
||
console.log(' Beneficiary #' + c.beneficiaryId + ' → legacy_deployment_id=' + c.legacyDeploymentId);
|
||
}
|
||
}
|
||
|
||
if (failed.length > 0) {
|
||
console.log();
|
||
console.log('Ошибки:');
|
||
for (const f of failed) {
|
||
console.log(' Beneficiary #' + f.beneficiaryId + ': ' + f.error);
|
||
}
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error(' ❌ ОШИБКА БД:', error.message);
|
||
} finally {
|
||
await client.end();
|
||
}
|
||
|
||
console.log();
|
||
console.log('='.repeat(70));
|
||
console.log('Готово!');
|
||
}
|
||
|
||
main().catch(console.error);
|