## Database Migrations (18 new) - Migrate all primary keys from SERIAL to UUID - Add soft delete (deleted_at) to all 19 entities - Add missing indexes for performance optimization - Add CHECK constraints for data validation - Add user audit fields (last_login_at, timezone, locale) - Add weather station location fields (latitude, longitude, elevation) - Add foreign key relationships (CameraDevice→Device, ValidatedEvent→WeatherStation) - Prepare private key encryption fields ## Backend Entity Updates - All entities updated with UUID primary keys - Added @DeleteDateColumn for soft delete support - Updated relations and foreign key types ## Backend Service/Controller Updates - Changed ID parameters from number to string (UUID) - Removed ParseIntPipe from controllers - Updated TypeORM queries for string IDs ## Frontend Updates - Updated all service interfaces to use string IDs - Fixed CameraDevice.location as JSONB object - Updated weather.ts with new fields (elevation, timezone) - Added Supabase integration hooks and lib - Fixed chart components for new data structure ## Cleanup - Removed deprecated .claude/agents configuration files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
142 lines
3.8 KiB
JavaScript
142 lines
3.8 KiB
JavaScript
/**
|
|
* Migration: Add missing user audit and tracking fields
|
|
*
|
|
* Fixes: Missing important user fields (last_login_at, timezone, email_verified_at)
|
|
*
|
|
* @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> | void}
|
|
*/
|
|
export const up = (pgm) => {
|
|
console.log('Adding user audit and tracking fields...');
|
|
|
|
// Add fields to user_profiles
|
|
pgm.addColumns('user_profiles', {
|
|
last_login_at: {
|
|
type: 'timestamptz',
|
|
notNull: false,
|
|
comment: 'Timestamp of last successful login',
|
|
},
|
|
timezone: {
|
|
type: 'varchar(50)',
|
|
notNull: false,
|
|
default: 'UTC',
|
|
comment: 'User timezone preference (e.g., Asia/Shanghai)',
|
|
},
|
|
locale: {
|
|
type: 'varchar(10)',
|
|
notNull: false,
|
|
default: 'zh-CN',
|
|
comment: 'User locale preference (e.g., en-US, zh-CN)',
|
|
},
|
|
login_count: {
|
|
type: 'integer',
|
|
notNull: true,
|
|
default: 0,
|
|
comment: 'Total number of logins',
|
|
},
|
|
});
|
|
|
|
// Add fields to user_identities
|
|
pgm.addColumns('user_identities', {
|
|
email_verified_at: {
|
|
type: 'timestamptz',
|
|
notNull: false,
|
|
comment: 'Timestamp when email was verified',
|
|
},
|
|
last_auth_at: {
|
|
type: 'timestamptz',
|
|
notNull: false,
|
|
comment: 'Timestamp of last authentication attempt',
|
|
},
|
|
auth_failure_count: {
|
|
type: 'integer',
|
|
notNull: true,
|
|
default: 0,
|
|
comment: 'Count of consecutive authentication failures',
|
|
},
|
|
locked_until: {
|
|
type: 'timestamptz',
|
|
notNull: false,
|
|
comment: 'Account locked until this timestamp (for brute-force protection)',
|
|
},
|
|
password_changed_at: {
|
|
type: 'timestamptz',
|
|
notNull: false,
|
|
comment: 'Timestamp when password was last changed',
|
|
},
|
|
});
|
|
|
|
// Add CHECK constraint for auth_failure_count
|
|
pgm.addConstraint('user_identities', 'chk_user_identities_auth_failure_count', {
|
|
check: 'auth_failure_count >= 0',
|
|
});
|
|
|
|
// Add CHECK constraint for login_count
|
|
pgm.addConstraint('user_profiles', 'chk_user_profiles_login_count', {
|
|
check: 'login_count >= 0',
|
|
});
|
|
|
|
// Add index for locked accounts query
|
|
pgm.createIndex('user_identities', 'locked_until', {
|
|
name: 'idx_user_identities_locked_until',
|
|
where: 'locked_until IS NOT NULL',
|
|
});
|
|
|
|
// Add index for unverified emails
|
|
pgm.createIndex('user_identities', 'email_verified_at', {
|
|
name: 'idx_user_identities_email_unverified',
|
|
where: "email IS NOT NULL AND email_verified_at IS NULL AND provider = 'email'",
|
|
});
|
|
|
|
console.log('User audit and tracking fields added successfully.');
|
|
};
|
|
|
|
/**
|
|
* @param pgm {import('node-pg-migrate').MigrationBuilder}
|
|
* @param run {() => void | undefined}
|
|
* @returns {Promise<void> | void}
|
|
*/
|
|
export const down = (pgm) => {
|
|
console.log('Removing user audit and tracking fields...');
|
|
|
|
// Drop indexes
|
|
pgm.dropIndex('user_identities', 'locked_until', {
|
|
name: 'idx_user_identities_locked_until',
|
|
ifExists: true,
|
|
});
|
|
|
|
pgm.dropIndex('user_identities', 'email_verified_at', {
|
|
name: 'idx_user_identities_email_unverified',
|
|
ifExists: true,
|
|
});
|
|
|
|
// Drop constraints
|
|
pgm.dropConstraint('user_identities', 'chk_user_identities_auth_failure_count', {
|
|
ifExists: true,
|
|
});
|
|
|
|
pgm.dropConstraint('user_profiles', 'chk_user_profiles_login_count', {
|
|
ifExists: true,
|
|
});
|
|
|
|
// Drop columns from user_identities
|
|
pgm.dropColumns('user_identities', [
|
|
'email_verified_at',
|
|
'last_auth_at',
|
|
'auth_failure_count',
|
|
'locked_until',
|
|
'password_changed_at',
|
|
]);
|
|
|
|
// Drop columns from user_profiles
|
|
pgm.dropColumns('user_profiles', ['last_login_at', 'timezone', 'locale', 'login_count']);
|
|
|
|
console.log('User audit and tracking fields removed.');
|
|
};
|