Created backend/src/config/constants.js to centralize all magic numbers and configuration values used throughout the backend codebase. Changes: - Created constants.js with organized sections for: - SECURITY: JWT, rate limiting, password reset - AUTH: OTP configuration and rate limiting - SERVER: Port, body limits, startup delays - MQTT: Connection settings, cache limits - NOTIFICATIONS: Push settings, quiet hours, batching - SERIAL: Validation patterns and constraints - EMAIL: Template settings and defaults - CRON: Schedule configurations - STORAGE: Avatar storage settings - Updated files to use constants: - index.js: JWT validation, rate limits, startup delays - routes/auth.js: OTP generation, rate limits, JWT expiry - services/mqtt.js: Connection timeouts, cache size - services/notifications.js: Batch size, TTL, quiet hours - utils/serialValidation.js: Serial number constraints - Added comprehensive test suite (30 tests) for constants module - All tests passing (93 total including existing tests) - Validates reasonable values and consistency between related constants Benefits: - Single source of truth for configuration values - Easier to maintain and update settings - Better documentation of what each value represents - Improved code readability by removing hardcoded numbers - Testable configuration values 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
136 lines
3.1 KiB
JavaScript
136 lines
3.1 KiB
JavaScript
/**
|
|
* Serial Number Validation Utilities (Backend)
|
|
*
|
|
* WellNuo device serial numbers follow formats:
|
|
* - Production: WELLNUO-XXXX-XXXX (16 chars with hyphens)
|
|
* - Demo: DEMO-00000 or DEMO-1234-5678
|
|
* - Legacy: 8+ alphanumeric characters
|
|
*/
|
|
|
|
const { SERIAL } = require('../config/constants');
|
|
|
|
// Demo serial numbers
|
|
const DEMO_SERIALS = new Set([
|
|
'DEMO-00000',
|
|
'DEMO-1234-5678',
|
|
]);
|
|
|
|
/**
|
|
* Validates a serial number and returns detailed result
|
|
* @param {string|null|undefined} serial - Serial number to validate
|
|
* @returns {{isValid: boolean, format: string, normalized: string, error?: string}}
|
|
*/
|
|
function validateSerial(serial) {
|
|
// Handle empty input
|
|
if (!serial || typeof serial !== 'string') {
|
|
return {
|
|
isValid: false,
|
|
format: 'invalid',
|
|
normalized: '',
|
|
error: 'Serial number is required',
|
|
};
|
|
}
|
|
|
|
// Normalize: trim and uppercase
|
|
const normalized = serial.trim().toUpperCase();
|
|
|
|
if (normalized.length === 0) {
|
|
return {
|
|
isValid: false,
|
|
format: 'invalid',
|
|
normalized: '',
|
|
error: 'Serial number is required',
|
|
};
|
|
}
|
|
|
|
// Check for demo serial
|
|
if (DEMO_SERIALS.has(normalized)) {
|
|
return {
|
|
isValid: true,
|
|
format: 'demo',
|
|
normalized,
|
|
};
|
|
}
|
|
|
|
// Production format: WELLNUO-XXXX-XXXX
|
|
// Must be exactly 16 characters with hyphens at positions 7 and 12
|
|
if (SERIAL.PRODUCTION_PATTERN.test(normalized)) {
|
|
return {
|
|
isValid: true,
|
|
format: 'production',
|
|
normalized,
|
|
};
|
|
}
|
|
|
|
// Legacy format: minimum 8 alphanumeric characters (no special characters except hyphens)
|
|
if (SERIAL.LEGACY_PATTERN.test(normalized)) {
|
|
return {
|
|
isValid: true,
|
|
format: 'legacy',
|
|
normalized,
|
|
};
|
|
}
|
|
|
|
// Invalid format
|
|
let error = 'Invalid serial number format';
|
|
|
|
if (normalized.length < SERIAL.MIN_LENGTH) {
|
|
error = `Serial number must be at least ${SERIAL.MIN_LENGTH} characters`;
|
|
} else if (!SERIAL.VALID_CHARS_PATTERN.test(normalized)) {
|
|
error = 'Serial number can only contain letters, numbers, and hyphens';
|
|
}
|
|
|
|
return {
|
|
isValid: false,
|
|
format: 'invalid',
|
|
normalized,
|
|
error,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Quick validation - returns true/false only
|
|
* @param {string|null|undefined} serial - Serial number to validate
|
|
* @returns {boolean}
|
|
*/
|
|
function isValidSerial(serial) {
|
|
return validateSerial(serial).isValid;
|
|
}
|
|
|
|
/**
|
|
* Check if serial is a demo serial
|
|
* @param {string|null|undefined} serial - Serial number to check
|
|
* @returns {boolean}
|
|
*/
|
|
function isDemoSerial(serial) {
|
|
if (!serial) return false;
|
|
const normalized = serial.trim().toUpperCase();
|
|
return DEMO_SERIALS.has(normalized);
|
|
}
|
|
|
|
/**
|
|
* Get user-friendly error message for invalid serial
|
|
* @param {string|null|undefined} serial - Serial number to validate
|
|
* @returns {string}
|
|
*/
|
|
function getSerialErrorMessage(serial) {
|
|
const result = validateSerial(serial);
|
|
|
|
if (result.isValid) {
|
|
return '';
|
|
}
|
|
|
|
if (result.error) {
|
|
return result.error;
|
|
}
|
|
|
|
return 'Invalid serial number format';
|
|
}
|
|
|
|
module.exports = {
|
|
validateSerial,
|
|
isValidSerial,
|
|
isDemoSerial,
|
|
getSerialErrorMessage,
|
|
};
|