-- ===================================================== -- Migration: Device Management System Cleanup -- Date: 2025-01-03 -- Purpose: Consolidate device tracking into single source of truth -- ===================================================== -- STEP 1: Backup existing data -- ===================================================== CREATE TABLE IF NOT EXISTS backup_device_data_20250103 AS SELECT 'activations' as source_table, a.id, a.license_id, COALESCE(a.device_name, a.machine_id) as device_name, a.hardware_hash as hardware_fingerprint, a.first_seen as first_activated_at, a.last_seen as last_seen_at, a.is_active, a.os_info, a.app_version FROM activations a UNION ALL SELECT 'device_registrations' as source_table, dr.id, dr.license_id, dr.device_name, dr.hardware_id as hardware_fingerprint, dr.first_seen as first_activated_at, dr.last_seen as last_seen_at, dr.is_active, dr.operating_system::jsonb as os_info, NULL as app_version FROM device_registrations dr; -- STEP 2: Standardize device_registrations table -- ===================================================== -- Add missing columns to device_registrations ALTER TABLE device_registrations ADD COLUMN IF NOT EXISTS hardware_fingerprint VARCHAR(255), ADD COLUMN IF NOT EXISTS app_version VARCHAR(20), ADD COLUMN IF NOT EXISTS metadata JSONB DEFAULT '{}', ADD COLUMN IF NOT EXISTS first_activated_at TIMESTAMP WITH TIME ZONE, ADD COLUMN IF NOT EXISTS last_seen_at TIMESTAMP WITH TIME ZONE; -- Migrate existing hardware_id to hardware_fingerprint UPDATE device_registrations SET hardware_fingerprint = hardware_id WHERE hardware_fingerprint IS NULL AND hardware_id IS NOT NULL; -- Migrate timestamp columns UPDATE device_registrations SET first_activated_at = first_seen, last_seen_at = last_seen WHERE first_activated_at IS NULL OR last_seen_at IS NULL; -- STEP 3: Migrate data from activations to device_registrations -- ===================================================== -- Insert activations that don't exist in device_registrations INSERT INTO device_registrations ( license_id, hardware_fingerprint, device_name, device_type, operating_system, first_activated_at, last_seen_at, is_active, ip_address, user_agent, app_version, metadata ) SELECT a.license_id, a.hardware_hash as hardware_fingerprint, COALESCE(a.device_name, a.machine_id) as device_name, CASE WHEN a.os_info->>'os' ILIKE '%windows%' THEN 'desktop' WHEN a.os_info->>'os' ILIKE '%mac%' THEN 'desktop' WHEN a.os_info->>'os' ILIKE '%linux%' THEN 'desktop' ELSE 'unknown' END as device_type, a.os_info->>'os' as operating_system, a.first_seen, a.last_seen, a.is_active, NULL as ip_address, NULL as user_agent, a.app_version, a.os_info as metadata FROM activations a WHERE NOT EXISTS ( SELECT 1 FROM device_registrations dr WHERE dr.license_id = a.license_id AND dr.hardware_fingerprint = a.hardware_hash ); -- Update existing device_registrations with latest data from activations UPDATE device_registrations dr SET last_seen_at = GREATEST(dr.last_seen_at, a.last_seen), is_active = a.is_active, app_version = COALESCE(a.app_version, dr.app_version), metadata = COALESCE(dr.metadata, '{}')::jsonb || COALESCE(a.os_info, '{}')::jsonb FROM activations a WHERE dr.license_id = a.license_id AND dr.hardware_fingerprint = a.hardware_hash AND a.last_seen > dr.last_seen_at; -- STEP 4: Standardize licenses table -- ===================================================== -- Remove duplicate device limit columns, keep only device_limit ALTER TABLE licenses DROP COLUMN IF EXISTS max_devices; ALTER TABLE licenses DROP COLUMN IF EXISTS max_activations; -- Ensure device_limit has proper constraints ALTER TABLE licenses DROP CONSTRAINT IF EXISTS licenses_device_limit_check, ADD CONSTRAINT licenses_device_limit_check CHECK (device_limit >= 1); -- Rename max_concurrent_sessions to concurrent_sessions_limit for clarity ALTER TABLE licenses RENAME COLUMN max_concurrent_sessions TO concurrent_sessions_limit; -- Update constraint to reference device_limit ALTER TABLE licenses DROP CONSTRAINT IF EXISTS check_concurrent_sessions, ADD CONSTRAINT check_concurrent_sessions CHECK (concurrent_sessions_limit <= device_limit); -- STEP 5: Clean up session tables -- ===================================================== -- Consolidate into single license_sessions table -- First, backup existing session data CREATE TABLE IF NOT EXISTS backup_sessions_20250103 AS SELECT id, license_id, license_key, session_id, username, computer_name, hardware_id, ip_address, user_agent, app_version, started_at, last_heartbeat, ended_at, is_active FROM sessions; -- Add missing columns to license_sessions ALTER TABLE license_sessions ADD COLUMN IF NOT EXISTS device_registration_id INTEGER REFERENCES device_registrations(id), ADD COLUMN IF NOT EXISTS ended_at TIMESTAMP WITH TIME ZONE, ADD COLUMN IF NOT EXISTS end_reason VARCHAR(50), ADD COLUMN IF NOT EXISTS user_agent TEXT; -- Link license_sessions to device_registrations UPDATE license_sessions ls SET device_registration_id = dr.id FROM device_registrations dr WHERE ls.license_id = dr.license_id AND ls.hardware_id = dr.hardware_fingerprint AND ls.device_registration_id IS NULL; -- STEP 6: Create indexes for performance -- ===================================================== CREATE INDEX IF NOT EXISTS idx_device_registrations_license_fingerprint ON device_registrations(license_id, hardware_fingerprint); CREATE INDEX IF NOT EXISTS idx_device_registrations_active ON device_registrations(license_id, is_active) WHERE is_active = true; CREATE INDEX IF NOT EXISTS idx_device_registrations_last_seen ON device_registrations(last_seen_at DESC); -- STEP 7: Drop old columns (after verification) -- ===================================================== -- These will be dropped after confirming migration success ALTER TABLE device_registrations DROP COLUMN IF EXISTS hardware_id, DROP COLUMN IF EXISTS first_seen, DROP COLUMN IF EXISTS last_seen; -- STEP 8: Create views for backwards compatibility (temporary) -- ===================================================== CREATE OR REPLACE VIEW v_activations AS SELECT id, license_id, device_name as machine_id, device_name, hardware_fingerprint as hardware_hash, first_activated_at as activation_date, first_activated_at as first_seen, last_seen_at as last_seen, last_seen_at as last_heartbeat, is_active, metadata as os_info, operating_system, app_version, ip_address, user_agent, device_type, deactivated_at, deactivated_by FROM device_registrations; -- STEP 9: Update system_api_key usage tracking -- ===================================================== UPDATE system_api_key SET last_used_at = CURRENT_TIMESTAMP, usage_count = usage_count + 1 WHERE id = 1; -- STEP 10: Add audit log entry for migration -- ===================================================== INSERT INTO audit_log ( timestamp, username, action, entity_type, entity_id, additional_info ) VALUES ( CURRENT_TIMESTAMP, 'system', 'device_management_migration', 'database', 0, 'Consolidated device management system - merged activations into device_registrations' ); -- Summary of changes: -- 1. Consolidated device tracking into device_registrations table -- 2. Removed duplicate columns: max_devices, max_activations -- 3. Standardized on device_limit column -- 4. Renamed max_concurrent_sessions to concurrent_sessions_limit -- 5. Added proper foreign key relationships -- 6. Created backwards compatibility view for activations -- 7. Improved indexing for performance