/** * Migration: Prepare UUID migration - Add new UUID columns * * Phase 1 of 3 for migrating Serial primary keys to UUID * This migration adds new UUID columns and generates values * * Affected tables: * - analysis_results * - weather_stations * - weather_forecasts * - weather_observations * - subscription_plans * - user_subscriptions * - subscription_history * - payment_records * - camera_devices * * @type {import('node-pg-migrate').ColumnDefinitions | undefined} */ export const shorthands = undefined; /** * Tables with serial primary keys that need UUID migration */ const TABLES_TO_MIGRATE = [ 'analysis_results', 'weather_stations', 'weather_forecasts', 'weather_observations', 'subscription_plans', 'user_subscriptions', 'subscription_history', 'payment_records', 'camera_devices', ]; /** * @param pgm {import('node-pg-migrate').MigrationBuilder} * @param run {() => void | undefined} * @returns {Promise | void} */ export const up = (pgm) => { console.log('Phase 1: Adding new UUID columns to tables with Serial primary keys...'); // Ensure pgcrypto extension is available for gen_random_uuid() pgm.sql('CREATE EXTENSION IF NOT EXISTS pgcrypto;'); // Add new_id UUID column to each table TABLES_TO_MIGRATE.forEach((table) => { console.log(` Adding new_id to ${table}...`); pgm.addColumn(table, { new_id: { type: 'uuid', notNull: false, comment: 'Temporary UUID column for migration from serial ID', }, }); // Generate UUID for all existing rows pgm.sql(`UPDATE ${table} SET new_id = gen_random_uuid() WHERE new_id IS NULL;`); // Create index on new_id for faster lookups during migration pgm.createIndex(table, 'new_id', { name: `idx_${table}_new_id`, unique: true, where: 'new_id IS NOT NULL', }); }); // Create mapping tables to preserve old_id -> new_id relationships // This is crucial for data migration and rollback pgm.sql(` -- Create mapping table for ID lookups during migration CREATE TABLE IF NOT EXISTS _migration_id_mapping ( table_name VARCHAR(100) NOT NULL, old_id INTEGER NOT NULL, new_id UUID NOT NULL, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (table_name, old_id) ); CREATE INDEX IF NOT EXISTS idx_migration_mapping_new_id ON _migration_id_mapping(table_name, new_id); `); // Populate mapping table TABLES_TO_MIGRATE.forEach((table) => { pgm.sql(` INSERT INTO _migration_id_mapping (table_name, old_id, new_id) SELECT '${table}', id, new_id FROM ${table} ON CONFLICT (table_name, old_id) DO UPDATE SET new_id = EXCLUDED.new_id; `); }); console.log('Phase 1 complete: UUID columns added and populated.'); }; /** * @param pgm {import('node-pg-migrate').MigrationBuilder} * @param run {() => void | undefined} * @returns {Promise | void} */ export const down = (pgm) => { console.log('Rolling back Phase 1: Removing UUID columns...'); // Drop mapping table pgm.sql('DROP TABLE IF EXISTS _migration_id_mapping CASCADE;'); // Remove new_id columns from all tables TABLES_TO_MIGRATE.forEach((table) => { console.log(` Removing new_id from ${table}...`); pgm.dropIndex(table, 'new_id', { name: `idx_${table}_new_id`, ifExists: true, }); pgm.dropColumn(table, 'new_id', { ifExists: true, }); }); console.log('Phase 1 rollback complete.'); };