/** * Migration: Add CHECK constraints for data integrity * * Fixes: Missing constraints on numeric ranges * * @type {import('node-pg-migrate').ColumnDefinitions | undefined} */ export const shorthands = undefined; /** * @param pgm {import('node-pg-migrate').MigrationBuilder} * @param run {() => void | undefined} * @returns {Promise | void} */ export const up = (pgm) => { console.log('Adding CHECK constraints...'); // devices.trust_score: should be between 0.0 and 1.0 pgm.addConstraint('devices', 'chk_devices_trust_score', { check: 'trust_score IS NULL OR (trust_score >= 0.0 AND trust_score <= 1.0)', }); // validated_events.validation_score: should be between 0 and 1 pgm.addConstraint('validated_events', 'chk_validated_events_validation_score', { check: 'validation_score IS NULL OR (validation_score >= 0 AND validation_score <= 1)', }); // validated_events.azimuth: should be between 0 and 360 pgm.addConstraint('validated_events', 'chk_validated_events_azimuth', { check: 'azimuth IS NULL OR (azimuth >= 0 AND azimuth <= 360)', }); // validated_events.altitude: should be between -90 and 90 (elevation angle) pgm.addConstraint('validated_events', 'chk_validated_events_altitude', { check: 'altitude IS NULL OR (altitude >= -90 AND altitude <= 90)', }); // validated_events.duration: should be positive pgm.addConstraint('validated_events', 'chk_validated_events_duration', { check: 'duration IS NULL OR duration >= 0', }); // validated_events.velocity: should be positive pgm.addConstraint('validated_events', 'chk_validated_events_velocity', { check: 'velocity IS NULL OR velocity >= 0', }); // device_security_events.risk_score: should be between 0 and 100 pgm.addConstraint('device_security_events', 'chk_security_events_risk_score', { check: 'risk_score >= 0 AND risk_score <= 100', }); // device_security_events.false_positive_probability: should be between 0 and 1 pgm.addConstraint('device_security_events', 'chk_security_events_fp_probability', { check: 'false_positive_probability >= 0.0 AND false_positive_probability <= 1.0', }); // device_certificates.key_size: should be valid key sizes pgm.addConstraint('device_certificates', 'chk_certificates_key_size', { check: 'key_size IN (1024, 2048, 3072, 4096, 256, 384, 521)', // RSA and EC key sizes }); // device_configurations.is_active: only one active config per device // This is enforced via partial unique index pgm.createIndex('device_configurations', ['device_id'], { name: 'idx_device_configurations_active_unique', unique: true, where: 'is_active = true', }); // subscription_plans.price: should be non-negative pgm.addConstraint('subscription_plans', 'chk_subscription_plans_price', { check: 'price >= 0', }); // subscription_plans.interval_count: should be positive pgm.addConstraint('subscription_plans', 'chk_subscription_plans_interval_count', { check: 'interval_count > 0', }); // payment_records.amount: should be positive pgm.addConstraint('payment_records', 'chk_payment_records_amount', { check: 'amount > 0', }); // device_registrations.retry_count: should be non-negative pgm.addConstraint('device_registrations', 'chk_registrations_retry_count', { check: 'retry_count >= 0', }); // camera_devices.exposure_count: should be non-negative pgm.addConstraint('camera_devices', 'chk_camera_devices_exposure_count', { check: 'exposure_count >= 0', }); console.log('CHECK constraints added successfully.'); }; /** * @param pgm {import('node-pg-migrate').MigrationBuilder} * @param run {() => void | undefined} * @returns {Promise | void} */ export const down = (pgm) => { console.log('Removing CHECK constraints...'); // Drop unique partial index pgm.dropIndex('device_configurations', ['device_id'], { name: 'idx_device_configurations_active_unique', ifExists: true, }); // Drop all CHECK constraints const constraints = [ { table: 'devices', name: 'chk_devices_trust_score' }, { table: 'validated_events', name: 'chk_validated_events_validation_score' }, { table: 'validated_events', name: 'chk_validated_events_azimuth' }, { table: 'validated_events', name: 'chk_validated_events_altitude' }, { table: 'validated_events', name: 'chk_validated_events_duration' }, { table: 'validated_events', name: 'chk_validated_events_velocity' }, { table: 'device_security_events', name: 'chk_security_events_risk_score' }, { table: 'device_security_events', name: 'chk_security_events_fp_probability' }, { table: 'device_certificates', name: 'chk_certificates_key_size' }, { table: 'subscription_plans', name: 'chk_subscription_plans_price' }, { table: 'subscription_plans', name: 'chk_subscription_plans_interval_count' }, { table: 'payment_records', name: 'chk_payment_records_amount' }, { table: 'device_registrations', name: 'chk_registrations_retry_count' }, { table: 'camera_devices', name: 'chk_camera_devices_exposure_count' }, ]; constraints.forEach(({ table, name }) => { pgm.dropConstraint(table, name, { ifExists: true }); }); console.log('CHECK constraints removed.'); };