/** * 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} 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} params.wifis - WiFi credentials ["SSID|password", ...] * @param {Array} params.devices - Device well_ids [497, 523] * @returns {Promise} 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} 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} 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} 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} 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} 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} 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 };