WellNuo/backend/src/services/legacyAPI.js
Sergei 0c82e05617 Switch email service from Brevo to Legacy API
- Add sendEmailViaLegacyAPI() function in legacyAPI.js
- Update email.js to use Legacy API instead of Brevo
- All emails (OTP, password reset, invitations) now go through
  eluxnetworks.net/function/well-api/api with send_message method
- Fix syntax errors in alarm.js, beneficiaries.js, invitations.js
  (missing console.log statements)

Brevo API is no longer used - credentials can be removed from .env

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-04 13:03:52 -08:00

355 lines
11 KiB
JavaScript

/**
* Legacy API Integration Service
*
* This service handles communication with the Legacy WellNuo API
* at eluxnetworks.net for device management and deployments.
*/
const axios = require('axios');
const LEGACY_API_BASE = 'https://eluxnetworks.net/function/well-api/api';
/**
* Room location codes mapping
* These numeric codes are used by Legacy API to identify room types
*/
const ROOM_LOCATIONS = {
'Bedroom': 102,
'Living Room': 103,
'Kitchen': 104,
'Bathroom': 105,
'Hallway': 106,
'Office': 107,
'Garage': 108,
'Dining Room': 109,
'Basement': 110,
'Other': 200
};
const LOCATION_NAMES = Object.fromEntries(
Object.entries(ROOM_LOCATIONS).map(([k, v]) => [v, k])
);
/**
* Get authentication token from Legacy API
* @param {string} username - Legacy API username
* @param {string} password - Legacy API password
* @returns {Promise<string>} Access token
*/
async function getLegacyToken(username, password) {
const formData = new URLSearchParams({
function: 'credentials',
user_name: username,
ps: password,
clientId: '001',
nonce: Date.now().toString()
});
const response = await axios.post(LEGACY_API_BASE, formData, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
if (response.data.status !== '200 OK') {
throw new Error('Legacy API authentication failed');
}
return response.data.access_token;
}
/**
* Create deployment in Legacy API
* @param {object} params - Deployment parameters
* @param {string} params.username - Installer username
* @param {string} params.token - Access token
* @param {string} params.beneficiaryName - Full name
* @param {string} params.beneficiaryEmail - Email
* @param {string} params.beneficiaryUsername - Login username
* @param {string} params.beneficiaryPassword - Password
* @param {string} params.address - Address
* @param {string} params.caretakerUsername - Caretaker username
* @param {string} params.caretakerEmail - Caretaker email
* @param {number} params.persons - Number of persons
* @param {number} params.pets - Number of pets
* @param {string} params.gender - Gender
* @param {number} params.race - Race index
* @param {number} params.born - Year born
* @param {number} params.lat - GPS latitude
* @param {number} params.lng - GPS longitude
* @param {Array<string>} params.wifis - WiFi credentials ["SSID|password", ...]
* @param {Array<number>} params.devices - Device well_ids [497, 523]
* @returns {Promise<number>} Created deployment_id
*/
async function createLegacyDeployment(params) {
// 1x1 pixel JPEG as base64 - required by Legacy API
const MINI_PHOTO = '/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAn/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCwAB//2Q==';
// Minimal params - exactly like working Postman request
const formData = new URLSearchParams({
function: 'set_deployment',
user_name: params.username,
token: params.token,
deployment: 'NEW',
beneficiary_name: params.beneficiaryName,
beneficiary_email: params.beneficiaryEmail,
beneficiary_user_name: params.beneficiaryUsername,
beneficiary_password: params.beneficiaryPassword,
beneficiary_address: params.address || 'test',
beneficiary_photo: MINI_PHOTO,
firstName: 'Test',
lastName: 'User',
first_name: 'Test',
last_name: 'User',
new_user_name: params.beneficiaryUsername,
phone_number: '+10000000000',
key: params.beneficiaryPassword,
signature: 'Test',
gps_age: '0',
wifis: '[]',
devices: '[]'
});
const response = await axios.post(LEGACY_API_BASE, formData, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
if (response.data.status !== '200 OK') {
throw new Error(`Failed to create deployment in Legacy API: ${response.data.status || JSON.stringify(response.data)}`);
}
// Extract deployment_id from response
// Response format varies, need to handle different cases
// Note: Current Legacy API returns only {ok: 1, status: "200 OK"} without deployment_id
const deploymentId = response.data.deployment_id || response.data.result || response.data.well_id;
if (!deploymentId) {
}
return deploymentId;
}
/**
* Assign device to deployment (set well_id)
* @param {string} username - Username
* @param {string} token - Access token
* @param {number} deviceId - Internal device ID
* @param {number} wellId - Well ID to assign
* @param {string} mac - Device MAC address
* @returns {Promise<boolean>} Success status
*/
async function assignDeviceToDeployment(username, token, deviceId, wellId, mac) {
const formData = new URLSearchParams({
function: 'device_set_well_id',
user_name: username,
token: token,
device_id: deviceId,
well_id: wellId,
mac: mac.toUpperCase().replace(/:/g, '')
});
const response = await axios.post(LEGACY_API_BASE, formData, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
return response.data.success === true;
}
/**
* Update device location/room
* @param {string} username - Username
* @param {string} token - Access token
* @param {number} wellId - Device well_id
* @param {string} deviceMac - MAC address
* @param {string} roomName - Room name (Bedroom, Kitchen, etc.)
* @param {object} options - Additional options
* @param {string} options.description - Device description
* @param {string} options.closeTo - Position description
* @param {number} options.radarThreshold - Radar sensitivity (0-100)
* @param {number} options.group - Group ID
* @returns {Promise<boolean>} Success status
*/
async function updateDeviceLocation(username, token, wellId, deviceMac, roomName, options = {}) {
const locationCode = ROOM_LOCATIONS[roomName] || ROOM_LOCATIONS['Other'];
const formData = new URLSearchParams({
function: 'device_form',
user_name: username,
token: token,
well_id: wellId,
device_mac: deviceMac.toUpperCase().replace(/:/g, ''),
location: locationCode.toString(),
description: options.description || '',
close_to: options.closeTo || '',
radar_threshold: options.radarThreshold || 50,
group: options.group || 0
});
const response = await axios.post(LEGACY_API_BASE, formData, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
return response.data.status === '200 OK';
}
/**
* Get devices for deployment (only recently active)
* @param {string} username - Username
* @param {string} token - Access token
* @param {number} deploymentId - Deployment ID
* @param {boolean} onlineOnly - If true, return only online devices (fresh=true)
* @returns {Promise<Array>} List of devices
*/
async function getDeploymentDevices(username, token, deploymentId, onlineOnly = true) {
const formData = new URLSearchParams({
function: 'request_devices',
user_name: username,
token: token,
deployment_id: deploymentId,
group_id: 'All',
location: 'All',
fresh: onlineOnly ? 'true' : 'false'
});
const response = await axios.post(LEGACY_API_BASE, formData, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
if (response.data.status !== '200 OK') {
return [];
}
// Parse device array format:
// [device_id, well_id, MAC, timestamp, location, description, deployment_id]
const devices = response.data.result_list || [];
return devices.map(device => ({
deviceId: device[0],
wellId: device[1],
mac: device[2],
lastSeen: device[3],
locationCode: device[4],
locationName: LOCATION_NAMES[device[4]] || 'Unknown',
description: device[5],
deploymentId: device[6],
isOnline: true // If fresh=true was used, all returned devices are online
}));
}
/**
* Reboot device
* @param {string} username - Username
* @param {string} token - Access token
* @param {number} deviceId - Device ID
* @returns {Promise<boolean>} Success status
*/
async function rebootDevice(username, token, deviceId) {
const formData = new URLSearchParams({
function: 'device_reboot',
user_name: username,
token: token,
device_id: deviceId
});
const response = await axios.post(LEGACY_API_BASE, formData, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
return response.data.status === '200 OK';
}
/**
* Find deployment ID by beneficiary username
* Used to retrieve deployment_id after creation (since set_deployment doesn't return it)
* @param {string} adminUsername - Admin username
* @param {string} adminToken - Admin access token
* @param {string} beneficiaryUsername - Username of the beneficiary
* @returns {Promise<number|null>} Deployment ID or null if not found
*/
async function findDeploymentByUsername(adminUsername, adminToken, beneficiaryUsername) {
try {
// Try logging in as the beneficiary user to get their deployment
// Note: This requires knowing the beneficiary's password
// Alternative: Use get_user_deployments if available
const formData = new URLSearchParams({
function: 'get_user_deployments',
user_name: adminUsername,
token: adminToken,
target_username: beneficiaryUsername
});
const response = await axios.post(LEGACY_API_BASE, formData, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
if (response.data.status === '200 OK' && response.data.result_list) {
// Return the first deployment ID
const deployments = response.data.result_list;
if (deployments.length > 0) {
return deployments[0].deployment_id || deployments[0][0]; // Handle both object and array format
}
}
return null;
} catch (error) {
return null;
}
}
/**
* Send email via Legacy API (eluxnetworks.net)
* Uses the send_message function with method=email
*
* @param {object} params - Email parameters
* @param {string} params.to - Recipient email address
* @param {string} params.subject - Email subject
* @param {string} params.content - Email content (HTML supported)
* @returns {Promise<boolean>} Success status
*/
async function sendEmailViaLegacyAPI({ to, subject, content }) {
const username = process.env.LEGACY_API_USERNAME;
const password = process.env.LEGACY_API_PASSWORD;
if (!username || !password) {
throw new Error('Legacy API credentials not configured');
}
// Get fresh token
const token = await getLegacyToken(username, password);
const formData = new URLSearchParams({
function: 'send_message',
token: token,
user_name: username,
user_id_or_recepient: to,
method: 'email',
subject: subject,
content: content
});
const response = await axios.post(LEGACY_API_BASE, formData, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
if (response.data.ok !== true && response.data.status !== '200 OK') {
throw new Error(`Failed to send email via Legacy API: ${JSON.stringify(response.data)}`);
}
return true;
}
module.exports = {
getLegacyToken,
createLegacyDeployment,
findDeploymentByUsername,
assignDeviceToDeployment,
updateDeviceLocation,
getDeploymentDevices,
rebootDevice,
sendEmailViaLegacyAPI,
ROOM_LOCATIONS,
LOCATION_NAMES
};