meteor_detection_system/meteor-web-backend/migrations/1766300000004_add-user-audit-fields.js
grabbit f557c06771 feat: complete database schema migration to UUID primary keys
## 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>
2025-12-21 03:33:26 +08:00

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.');
};