285 lines
7.4 KiB
Markdown
285 lines
7.4 KiB
Markdown
# Migration Workflow Guide
|
|
|
|
## Overview
|
|
This project uses a TypeScript-based migration system for database schema changes. All migrations are automatically executed when you start the development server.
|
|
|
|
## 🚀 Quick Start
|
|
|
|
### Running Development Server with Migrations
|
|
```bash
|
|
npm run dev
|
|
```
|
|
This command will:
|
|
1. ✅ Run all pending migrations automatically
|
|
2. 🚀 Start the development server with hot reload
|
|
|
|
### Running Migrations Only
|
|
```bash
|
|
npm run migrate
|
|
```
|
|
Use this when you only want to apply migrations without starting the server.
|
|
|
|
## 📝 Creating New Migrations
|
|
|
|
### Step 1: Create Migration File
|
|
Create a new TypeScript file in `src/migrations/` with the naming pattern:
|
|
```
|
|
YYYYMMDD-descriptive-name.ts
|
|
```
|
|
|
|
Example: `20251105-add-new-field.ts`
|
|
|
|
### Step 2: Migration Template
|
|
```typescript
|
|
import { QueryInterface, DataTypes } from 'sequelize';
|
|
|
|
/**
|
|
* Migration: Brief description
|
|
* Purpose: Detailed explanation
|
|
* Date: YYYY-MM-DD
|
|
*/
|
|
|
|
export async function up(queryInterface: QueryInterface): Promise<void> {
|
|
// Add your forward migration logic here
|
|
await queryInterface.addColumn('table_name', 'column_name', {
|
|
type: DataTypes.STRING,
|
|
allowNull: true,
|
|
});
|
|
|
|
console.log('✅ Migration description completed');
|
|
}
|
|
|
|
export async function down(queryInterface: QueryInterface): Promise<void> {
|
|
// Add your rollback logic here
|
|
await queryInterface.removeColumn('table_name', 'column_name');
|
|
|
|
console.log('✅ Migration rolled back');
|
|
}
|
|
```
|
|
|
|
### Step 3: Register Migration
|
|
Add your new migration to `src/scripts/migrate.ts`:
|
|
|
|
```typescript
|
|
// 1. Import at the top
|
|
import * as m15 from '../migrations/20251105-add-new-field';
|
|
|
|
// 2. Execute in the run() function
|
|
await (m15 as any).up(sequelize.getQueryInterface());
|
|
```
|
|
|
|
### Step 4: Test
|
|
```bash
|
|
npm run migrate
|
|
```
|
|
|
|
## 📋 Current Migrations
|
|
|
|
The following migrations are configured and will run in order:
|
|
|
|
1. `2025103001-create-workflow-requests` - Core workflow requests table
|
|
2. `2025103002-create-approval-levels` - Approval hierarchy structure
|
|
3. `2025103003-create-participants` - Workflow participants
|
|
4. `2025103004-create-documents` - Document attachments
|
|
5. `20251031_01_create_subscriptions` - User subscriptions
|
|
6. `20251031_02_create_activities` - Activity tracking
|
|
7. `20251031_03_create_work_notes` - Work notes/comments
|
|
8. `20251031_04_create_work_note_attachments` - Note attachments
|
|
9. `20251104-add-tat-alert-fields` - TAT alert fields
|
|
10. `20251104-create-tat-alerts` - TAT alerts table
|
|
11. `20251104-create-kpi-views` - KPI database views
|
|
12. `20251104-create-holidays` - Holiday calendar
|
|
13. `20251104-create-admin-config` - Admin configurations
|
|
14. `20251105-add-skip-fields-to-approval-levels` - Skip approver functionality
|
|
|
|
## 🔄 Migration Safety Features
|
|
|
|
### Idempotent Migrations
|
|
All migrations should be **idempotent** (safe to run multiple times). Use checks like:
|
|
|
|
```typescript
|
|
// Check if column exists before adding
|
|
const tableDescription = await queryInterface.describeTable('table_name');
|
|
if (!tableDescription.column_name) {
|
|
await queryInterface.addColumn(/* ... */);
|
|
}
|
|
|
|
// Check if table exists before creating
|
|
const tables = await queryInterface.showAllTables();
|
|
if (!tables.includes('table_name')) {
|
|
await queryInterface.createTable(/* ... */);
|
|
}
|
|
```
|
|
|
|
### Error Handling
|
|
Migrations automatically:
|
|
- ✅ Stop on first error
|
|
- ❌ Exit with error code 1 on failure
|
|
- 📝 Log detailed error messages
|
|
- 🔄 Prevent server startup if migrations fail
|
|
|
|
## 🛠️ Common Migration Operations
|
|
|
|
### Adding a Column
|
|
```typescript
|
|
await queryInterface.addColumn('table_name', 'new_column', {
|
|
type: DataTypes.STRING(100),
|
|
allowNull: false,
|
|
defaultValue: 'default_value',
|
|
comment: 'Column description'
|
|
});
|
|
```
|
|
|
|
### Adding Foreign Key
|
|
```typescript
|
|
await queryInterface.addColumn('table_name', 'foreign_key_id', {
|
|
type: DataTypes.UUID,
|
|
allowNull: true,
|
|
references: {
|
|
model: 'referenced_table',
|
|
key: 'id'
|
|
},
|
|
onUpdate: 'CASCADE',
|
|
onDelete: 'SET NULL'
|
|
});
|
|
```
|
|
|
|
### Creating Index
|
|
```typescript
|
|
await queryInterface.addIndex('table_name', ['column_name'], {
|
|
name: 'idx_table_column',
|
|
unique: false
|
|
});
|
|
|
|
// Partial index with WHERE clause
|
|
await queryInterface.addIndex('table_name', ['status'], {
|
|
name: 'idx_table_active',
|
|
where: {
|
|
is_active: true
|
|
}
|
|
});
|
|
```
|
|
|
|
### Creating Table
|
|
```typescript
|
|
await queryInterface.createTable('new_table', {
|
|
id: {
|
|
type: DataTypes.UUID,
|
|
defaultValue: DataTypes.UUIDV4,
|
|
primaryKey: true
|
|
},
|
|
name: {
|
|
type: DataTypes.STRING(100),
|
|
allowNull: false
|
|
},
|
|
created_at: {
|
|
type: DataTypes.DATE,
|
|
allowNull: false,
|
|
defaultValue: DataTypes.NOW
|
|
},
|
|
updated_at: {
|
|
type: DataTypes.DATE,
|
|
allowNull: false,
|
|
defaultValue: DataTypes.NOW
|
|
}
|
|
});
|
|
```
|
|
|
|
### Modifying Column
|
|
```typescript
|
|
await queryInterface.changeColumn('table_name', 'column_name', {
|
|
type: DataTypes.STRING(200), // Changed from 100
|
|
allowNull: true // Changed from false
|
|
});
|
|
```
|
|
|
|
### Dropping Column
|
|
```typescript
|
|
await queryInterface.removeColumn('table_name', 'old_column');
|
|
```
|
|
|
|
### Raw SQL Queries
|
|
```typescript
|
|
await queryInterface.sequelize.query(`
|
|
CREATE OR REPLACE VIEW view_name AS
|
|
SELECT * FROM table_name WHERE condition
|
|
`);
|
|
```
|
|
|
|
## 📊 Database Structure Reference
|
|
|
|
Always refer to `backend_structure.txt` for the authoritative database structure including:
|
|
- All tables and their columns
|
|
- Data types and constraints
|
|
- Relationships and foreign keys
|
|
- Enum values
|
|
- Indexes
|
|
|
|
## 🚨 Troubleshooting
|
|
|
|
### Migration Fails with "Column Already Exists"
|
|
- The migration might have partially run
|
|
- Add idempotent checks or manually rollback the failed migration
|
|
|
|
### Server Won't Start After Migration
|
|
- Check the migration error in console
|
|
- Fix the migration file
|
|
- Run `npm run migrate` to retry
|
|
|
|
### Need to Rollback a Migration
|
|
```bash
|
|
# Manual rollback (requires implementing down() function)
|
|
ts-node src/scripts/rollback.ts
|
|
```
|
|
|
|
## 🎯 Best Practices
|
|
|
|
1. **Always test migrations** on development database first
|
|
2. **Write rollback logic** in `down()` function
|
|
3. **Use descriptive names** for migrations
|
|
4. **Add comments** explaining the purpose
|
|
5. **Keep migrations small** - one logical change per file
|
|
6. **Never modify** existing migration files after they run in production
|
|
7. **Use transactions** for complex multi-step migrations
|
|
8. **Backup production** before running new migrations
|
|
|
|
## 📝 Migration Checklist
|
|
|
|
Before running migrations in production:
|
|
|
|
- [ ] Tested on local development database
|
|
- [ ] Verified rollback functionality works
|
|
- [ ] Checked for data loss scenarios
|
|
- [ ] Reviewed index impact on performance
|
|
- [ ] Confirmed migration is idempotent
|
|
- [ ] Updated `backend_structure.txt` documentation
|
|
- [ ] Added migration to version control
|
|
- [ ] Registered in `migrate.ts`
|
|
|
|
## 🔗 Related Files
|
|
|
|
- **Migration Scripts**: `src/migrations/`
|
|
- **Migration Runner**: `src/scripts/migrate.ts`
|
|
- **Database Config**: `src/config/database.ts`
|
|
- **Database Structure**: `backend_structure.txt`
|
|
- **Package Scripts**: `package.json`
|
|
|
|
## 💡 Example: Recent Migration
|
|
|
|
The latest migration (`20251105-add-skip-fields-to-approval-levels`) demonstrates best practices:
|
|
|
|
- ✅ Descriptive naming
|
|
- ✅ Clear documentation
|
|
- ✅ Multiple related columns added together
|
|
- ✅ Foreign key relationships
|
|
- ✅ Indexed for query performance
|
|
- ✅ Includes rollback logic
|
|
- ✅ Helpful console messages
|
|
|
|
---
|
|
|
|
**Last Updated**: November 5, 2025
|
|
**Migration Count**: 14 migrations
|
|
**Auto-Run**: Enabled for `npm run dev`
|
|
|