/** * Migration: Add CameraDevice foreign key to devices * * Fixes: CameraDevice has device_id as varchar but no FK to devices table * Changes: * - Change device_id from varchar(100) to uuid * - Add FK constraint to devices table * - Change location from text to jsonb * * @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 CameraDevice FK to devices...'); // Step 1: Add new UUID device_id column pgm.addColumn('camera_devices', { device_uuid: { type: 'uuid', notNull: false, comment: 'Foreign key to devices table', }, }); // Step 2: Try to migrate existing data if device_id matches a device // Note: This assumes device_id contains hardware_id or similar identifier pgm.sql(` -- Try to match existing device_id to devices by hardware_id UPDATE camera_devices cd SET device_uuid = d.id FROM devices d WHERE cd.device_id = d.hardware_id OR cd.device_id = d.id::text; `); // Step 3: Rename old device_id to legacy_device_id pgm.renameColumn('camera_devices', 'device_id', 'legacy_device_id'); // Step 4: Rename device_uuid to device_id pgm.renameColumn('camera_devices', 'device_uuid', 'device_id'); // Step 5: Add FK constraint (nullable for cameras without matching device) pgm.addConstraint('camera_devices', 'camera_devices_device_id_fkey', { foreignKeys: { columns: 'device_id', references: 'devices(id)', onDelete: 'SET NULL', }, }); // Step 6: Add index on device_id pgm.createIndex('camera_devices', 'device_id', { name: 'idx_camera_devices_device_id', where: 'device_id IS NOT NULL', }); // Step 7: Convert location from text to jsonb pgm.addColumn('camera_devices', { location_jsonb: { type: 'jsonb', notNull: false, comment: 'Structured location data {latitude, longitude, altitude, site_name}', }, }); // Migrate existing location text to jsonb pgm.sql(` UPDATE camera_devices SET location_jsonb = jsonb_build_object('site_name', location) WHERE location IS NOT NULL AND location != ''; `); // Rename columns pgm.renameColumn('camera_devices', 'location', 'legacy_location'); pgm.renameColumn('camera_devices', 'location_jsonb', 'location'); console.log('CameraDevice FK and location migration complete.'); }; /** * @param pgm {import('node-pg-migrate').MigrationBuilder} * @param run {() => void | undefined} * @returns {Promise | void} */ export const down = (pgm) => { console.log('Rolling back CameraDevice FK changes...'); // Restore location pgm.renameColumn('camera_devices', 'location', 'location_jsonb'); pgm.renameColumn('camera_devices', 'legacy_location', 'location'); pgm.dropColumn('camera_devices', 'location_jsonb', { ifExists: true }); // Restore device_id pgm.dropIndex('camera_devices', 'device_id', { name: 'idx_camera_devices_device_id', ifExists: true, }); pgm.dropConstraint('camera_devices', 'camera_devices_device_id_fkey', { ifExists: true, }); pgm.renameColumn('camera_devices', 'device_id', 'device_uuid'); pgm.renameColumn('camera_devices', 'legacy_device_id', 'device_id'); pgm.dropColumn('camera_devices', 'device_uuid', { ifExists: true }); console.log('CameraDevice FK rollback complete.'); };