Reorganize migrations for new vs existing deployments
Mirror to GitHub / mirror (push) Successful in 27s
Test and Lint / backend-test (push) Successful in 1m21s
continuous-integration/drone/push Build is passing
Test and Lint / frontend-test (push) Successful in 2m4s
Version and Release / version-bump (push) Failing after 31s
Version and Release / trigger-drone (push) Has been skipped

- Created core/ directory for essential migrations that always run
- Created legacy/ directory for migrations only needed when upgrading
- New deployments will only run core migrations for a clean database
- Existing deployments will run all migrations in proper sequence
- Fixed duplicate migration numbers (014 and 027)
- Updated migration runners to handle new directory structure
- Added README explaining the migration organization

This change optimizes deployment for new users who will get a clean
schema without running unnecessary upgrade migrations.
This commit is contained in:
2025-07-24 22:40:25 +02:00
parent 0c989ce086
commit 9854ca2f59
60 changed files with 158 additions and 2194 deletions
+46
View File
@@ -0,0 +1,46 @@
# Database Migrations
This directory contains database migrations for the Wedding Photo Sharing platform.
## Directory Structure
### `/core`
Essential migrations that are always run for new deployments. These include:
- `init.js` - Initial database schema creation
- Backup service tables (029-035)
- Gallery feedback tables (033)
### `/legacy`
Migrations needed only when upgrading from older versions. New deployments can skip these as the core schema already includes all necessary tables and columns.
## For New Deployments
If you're deploying this application for the first time:
1. The `initializeDatabase()` function in `src/database/db.js` will create all necessary tables
2. Only migrations in the `/core` directory will be run
3. This ensures a clean, optimized database schema
## For Existing Deployments
If you're upgrading from an older version:
1. All migrations (both core and legacy) will be run in sequence
2. The migration system tracks which migrations have been applied
3. Only new migrations will be executed
## Running Migrations
```bash
# Development
npm run migrate
# Production
npm run migrate:prod
```
## Note on Duplicate Migration Numbers
The legacy directory contains renamed duplicates:
- `014_add_host_name_to_events_duplicate.js` (was duplicate of 014)
- `027_add_rate_limit_settings_duplicate.js` (was duplicate of 027)
These have been renamed to avoid conflicts while preserving the migration history.
+61 -17
View File
@@ -42,6 +42,8 @@ async function detectExistingSchema() {
{ table: 'cms_pages', migration: '004_add_categories_and_cms.js' },
{ table: 'login_attempts', migration: '015_add_login_attempts_table.js' },
{ table: 'token_blacklist', migration: '017_add_token_revocation_tables.js' },
{ table: 'backup_runs', migration: '029_add_backup_service_tables.js' },
{ table: 'gallery_feedback', migration: '033_add_gallery_feedback.js' },
];
for (const check of tableChecks) {
@@ -56,13 +58,14 @@ async function detectExistingSchema() {
}
// Run a single migration safely
async function runMigrationSafely(filename) {
async function runMigrationSafely(filepath) {
try {
const migrationPath = path.join(__dirname, filename);
const migrationPath = path.join(__dirname, filepath);
const migration = require(migrationPath);
const filename = path.basename(filepath);
if (migration.up) {
console.log(`Running migration: ${filename}`);
console.log(`Running migration: ${filepath}`);
// Run migration in a transaction if possible
if (db.client.config.client === 'pg') {
@@ -74,14 +77,14 @@ async function runMigrationSafely(filename) {
}
await db('migrations').insert({ filename });
console.log(`Migration ${filename} completed successfully`);
console.log(`Migration ${filepath} completed successfully`);
}
} catch (error) {
// Check if error is because schema already exists
if (error.code === '42P07' || // PostgreSQL: relation already exists
error.code === 'SQLITE_ERROR' && error.message.includes('already exists')) {
console.log(`Migration ${filename} - schema already exists, marking as applied`);
await markMigrationAsApplied(filename);
console.log(`Migration ${filepath} - schema already exists, marking as applied`);
await markMigrationAsApplied(path.basename(filepath));
} else {
throw error;
}
@@ -104,23 +107,64 @@ async function runMigrations() {
// Detect and mark existing schema
await detectExistingSchema();
// Get all migration files
const files = await fs.readdir(__dirname);
const migrationFiles = files
.filter(f => f.match(/^\d{3}_.*\.js$/) || f === 'init.js')
.sort((a, b) => {
// Ensure init.js runs first
if (a === 'init.js') return -1;
if (b === 'init.js') return 1;
return a.localeCompare(b);
});
// Get applied migrations
const appliedMigrations = await db('migrations').select('filename');
const appliedFilenames = appliedMigrations.map(m => m.filename);
// Check if this is a new deployment (no migrations have been applied)
const isNewDeployment = appliedFilenames.length === 0;
// Get migration files from appropriate directories
let migrationFiles = [];
if (isNewDeployment) {
// For new deployments, only run core migrations
console.log('New deployment detected - running core migrations only');
const coreDir = path.join(__dirname, 'core');
const coreFiles = await fs.readdir(coreDir);
migrationFiles = coreFiles
.filter(f => f.match(/^\d{3}_.*\.js$/) || f === 'init.js')
.map(f => path.join('core', f))
.sort();
} else {
// For existing deployments, run all migrations (legacy + core)
console.log('Existing deployment detected - checking all migrations');
// Get legacy migrations
const legacyDir = path.join(__dirname, 'legacy');
const legacyFiles = await fs.readdir(legacyDir);
const legacyMigrations = legacyFiles
.filter(f => f.match(/^\d{3}_.*\.js$/))
.map(f => path.join('legacy', f));
// Get core migrations
const coreDir = path.join(__dirname, 'core');
const coreFiles = await fs.readdir(coreDir);
const coreMigrations = coreFiles
.filter(f => f.match(/^\d{3}_.*\.js$/) || f === 'init.js')
.map(f => path.join('core', f));
// Combine and sort by number
migrationFiles = [...legacyMigrations, ...coreMigrations]
.sort((a, b) => {
const baseA = path.basename(a);
const baseB = path.basename(b);
// Ensure init.js runs first
if (baseA === 'init.js') return -1;
if (baseB === 'init.js') return 1;
const numA = parseInt(baseA.split('_')[0]);
const numB = parseInt(baseB.split('_')[0]);
return numA - numB;
});
}
// Run pending migrations
let pendingCount = 0;
let skippedCount = 0;
for (const file of migrationFiles) {
const isApplied = await isMigrationApplied(file);
const filename = path.basename(file);
const isApplied = appliedFilenames.includes(filename);
if (!isApplied) {
await runMigrationSafely(file);
pendingCount++;
+49 -11
View File
@@ -22,15 +22,16 @@ async function getAppliedMigrations() {
}
// Run a single migration
async function runMigration(filename) {
const migrationPath = path.join(__dirname, filename);
async function runMigration(filepath) {
const migrationPath = path.join(__dirname, filepath);
const migration = require(migrationPath);
const filename = path.basename(filepath);
if (migration.up) {
console.log(`Running migration: ${filename}`);
console.log(`Running migration: ${filepath}`);
await migration.up(db);
await db('migrations').insert({ filename });
console.log(`Migration ${filename} completed`);
console.log(`Migration ${filepath} completed`);
}
}
@@ -50,19 +51,56 @@ async function runMigrations() {
// Create migrations table
await createMigrationsTable();
// Get all migration files
const files = await fs.readdir(__dirname);
const migrationFiles = files
.filter(f => f.match(/^\d{3}_.*\.js$/))
.sort();
// Get applied migrations
const appliedMigrations = await getAppliedMigrations();
// Check if this is a new deployment (no migrations have been applied)
const isNewDeployment = appliedMigrations.length === 0;
// Get migration files from appropriate directories
let migrationFiles = [];
if (isNewDeployment) {
// For new deployments, only run core migrations
console.log('New deployment detected - running core migrations only');
const coreDir = path.join(__dirname, 'core');
const coreFiles = await fs.readdir(coreDir);
migrationFiles = coreFiles
.filter(f => f.match(/^\d{3}_.*\.js$/))
.map(f => path.join('core', f))
.sort();
} else {
// For existing deployments, run all migrations (legacy + core)
console.log('Existing deployment detected - checking all migrations');
// Get legacy migrations
const legacyDir = path.join(__dirname, 'legacy');
const legacyFiles = await fs.readdir(legacyDir);
const legacyMigrations = legacyFiles
.filter(f => f.match(/^\d{3}_.*\.js$/))
.map(f => path.join('legacy', f));
// Get core migrations
const coreDir = path.join(__dirname, 'core');
const coreFiles = await fs.readdir(coreDir);
const coreMigrations = coreFiles
.filter(f => f.match(/^\d{3}_.*\.js$/))
.map(f => path.join('core', f));
// Combine and sort by number
migrationFiles = [...legacyMigrations, ...coreMigrations]
.sort((a, b) => {
const numA = parseInt(path.basename(a).split('_')[0]);
const numB = parseInt(path.basename(b).split('_')[0]);
return numA - numB;
});
}
// Run pending migrations
let pendingCount = 0;
for (const file of migrationFiles) {
if (!appliedMigrations.includes(file)) {
const filename = path.basename(file);
if (!appliedMigrations.includes(filename)) {
await runMigration(file);
pendingCount++;
}