/** * @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) => { // 1. Create subscription_plans table pgm.createTable('subscription_plans', { id: { type: 'serial', primaryKey: true }, plan_id: { type: 'varchar(50)', notNull: true, unique: true }, name: { type: 'varchar(100)', notNull: true }, description: { type: 'text' }, price: { type: 'decimal(10,2)', notNull: true }, currency: { type: 'varchar(10)', default: "'CNY'" }, interval: { type: 'varchar(20)', notNull: true }, interval_count: { type: 'integer', default: 1 }, stripe_price_id: { type: 'varchar(100)', unique: true }, features: { type: 'jsonb' }, is_popular: { type: 'boolean', default: false }, is_active: { type: 'boolean', default: true }, created_at: { type: 'timestamptz', default: pgm.func('NOW()') }, updated_at: { type: 'timestamptz', default: pgm.func('NOW()') }, }); // 2. Create user_subscriptions table pgm.createTable('user_subscriptions', { id: { type: 'serial', primaryKey: true }, user_profile_id: { type: 'uuid', notNull: true, references: 'user_profiles(id)', onDelete: 'CASCADE', }, subscription_plan_id: { type: 'integer', notNull: true, references: 'subscription_plans(id)', onDelete: 'CASCADE', }, stripe_subscription_id: { type: 'varchar(100)', unique: true }, status: { type: 'varchar(20)', notNull: true, default: "'active'" }, current_period_start: { type: 'timestamptz' }, current_period_end: { type: 'timestamptz' }, cancel_at_period_end: { type: 'boolean', default: false }, canceled_at: { type: 'timestamptz' }, trial_start: { type: 'timestamptz' }, trial_end: { type: 'timestamptz' }, created_at: { type: 'timestamptz', default: pgm.func('NOW()') }, updated_at: { type: 'timestamptz', default: pgm.func('NOW()') }, }); // 3. Create subscription_history table pgm.createTable('subscription_history', { id: { type: 'serial', primaryKey: true }, user_subscription_id: { type: 'integer', notNull: true, references: 'user_subscriptions(id)', onDelete: 'CASCADE', }, action: { type: 'varchar(50)', notNull: true }, old_status: { type: 'varchar(20)' }, new_status: { type: 'varchar(20)' }, metadata: { type: 'jsonb' }, created_at: { type: 'timestamptz', default: pgm.func('NOW()') }, }); // 4. Create payment_records table pgm.createTable('payment_records', { id: { type: 'serial', primaryKey: true }, user_subscription_id: { type: 'integer', notNull: true, references: 'user_subscriptions(id)', onDelete: 'CASCADE', }, stripe_payment_intent_id: { type: 'varchar(100)', unique: true }, amount: { type: 'decimal(10,2)', notNull: true }, currency: { type: 'varchar(10)', notNull: true }, status: { type: 'varchar(20)', notNull: true }, payment_method: { type: 'varchar(50)' }, failure_reason: { type: 'text' }, paid_at: { type: 'timestamptz' }, created_at: { type: 'timestamptz', default: pgm.func('NOW()') }, }); // 5. Create camera_devices table pgm.createTable('camera_devices', { id: { type: 'serial', primaryKey: true }, device_id: { type: 'varchar(100)', notNull: true, unique: true }, name: { type: 'varchar(100)', notNull: true }, location: { type: 'varchar(200)', notNull: true }, status: { type: 'varchar(20)', default: "'offline'" }, last_seen_at: { type: 'timestamptz' }, temperature: { type: 'decimal(5,2)' }, cooler_power: { type: 'decimal(5,2)' }, gain: { type: 'integer' }, exposure_count: { type: 'integer', default: 0 }, uptime: { type: 'decimal(10,2)' }, firmware_version: { type: 'varchar(50)' }, serial_number: { type: 'varchar(100)', unique: true }, created_at: { type: 'timestamptz', default: pgm.func('NOW()') }, updated_at: { type: 'timestamptz', default: pgm.func('NOW()') }, }); // 6. Create weather_observations table pgm.createTable('weather_observations', { id: { type: 'serial', primaryKey: true }, weather_station_id: { type: 'integer', notNull: true, references: 'weather_stations(id)', onDelete: 'CASCADE', }, observation_time: { type: 'timestamptz', notNull: true }, temperature: { type: 'decimal(5,2)', notNull: true }, humidity: { type: 'decimal(5,2)', notNull: true }, cloud_cover: { type: 'decimal(5,2)', notNull: true }, visibility: { type: 'decimal(6,2)', notNull: true }, wind_speed: { type: 'decimal(5,2)', notNull: true }, wind_direction: { type: 'integer', notNull: true }, condition: { type: 'varchar(50)', notNull: true }, observation_quality: { type: 'varchar(20)', notNull: true }, pressure: { type: 'decimal(7,2)', notNull: true }, precipitation: { type: 'decimal(5,2)', notNull: true }, created_at: { type: 'timestamptz', default: pgm.func('NOW()') }, }); // Create indexes for foreign keys pgm.createIndex('user_subscriptions', 'user_profile_id'); pgm.createIndex('user_subscriptions', 'subscription_plan_id'); pgm.createIndex('subscription_history', 'user_subscription_id'); pgm.createIndex('payment_records', 'user_subscription_id'); pgm.createIndex('weather_observations', 'weather_station_id'); pgm.createIndex('weather_observations', 'observation_time'); }; /** * @param pgm {import('node-pg-migrate').MigrationBuilder} * @param run {() => void | undefined} * @returns {Promise | void} */ export const down = (pgm) => { // Drop tables in reverse order due to foreign key constraints pgm.dropTable('weather_observations', { ifExists: true, cascade: true }); pgm.dropTable('camera_devices', { ifExists: true, cascade: true }); pgm.dropTable('payment_records', { ifExists: true, cascade: true }); pgm.dropTable('subscription_history', { ifExists: true, cascade: true }); pgm.dropTable('user_subscriptions', { ifExists: true, cascade: true }); pgm.dropTable('subscription_plans', { ifExists: true, cascade: true }); };