WellNuo/backend/create-legacy-deployments.js
Sergei f6ba2a906a Fix race conditions when quickly switching beneficiaries
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>
2026-01-29 12:33:57 -08:00

305 lines
9.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Скрипт для массового создания 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);