refactor(migrations): squash 75 migrations into 12 per-module initial migrations

The old migration chain was broken (downgrade path through vendor->merchant
rename made rollbacks impossible). This squashes everything into fresh
per-module migrations with zero schema drift, verified by autogenerate.

Changes:
- Replace 75 accumulated migrations with 12 per-module initial migrations
  (core, billing, catalog, marketplace, cms, customers, orders, inventory,
  cart, messaging, loyalty, dev_tools) in a linear chain
- Fix make db-reset to use SQL DROP SCHEMA instead of alembic downgrade base
- Enable migration autodiscovery for all modules (migrations_path in definitions)
- Rewrite alembic/env.py to import all 75 model tables across 13 modules
- Fix AdminNotification import (was incorrectly from tenancy, now from messaging)
- Update squash_migrations.py to handle all module migration directories

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-08 11:51:37 +01:00
parent dad02695f6
commit c3d26e9aa4
111 changed files with 2285 additions and 11723 deletions

View File

@@ -2,7 +2,7 @@
"""
Migration Squash Script
This script squashes all existing migrations into a single initial migration.
This script squashes all existing migrations into fresh per-module initial migrations.
Run this after setting up PostgreSQL to simplify the migration history.
Prerequisites:
@@ -13,13 +13,13 @@ Usage:
python scripts/squash_migrations.py
What this script does:
1. Backs up existing migrations to alembic/versions_backup_YYYYMMDD/
1. Backs up existing migrations from all version_locations to a timestamped backup
2. Creates a fresh initial migration from current models
3. Stamps the database as being at the new migration
After running:
1. Review the new migration in alembic/versions/
2. Test with: make migrate-up (on a fresh database)
1. Review the new migration files
2. Test with: make db-reset (drops schema, runs all migrations, seeds data)
3. If satisfied, delete the backup directory
"""
@@ -36,6 +36,22 @@ sys.path.insert(0, str(project_root))
VERSIONS_DIR = project_root / "alembic" / "versions"
# All migration version directories (core + modules)
MODULE_MIGRATION_DIRS = [
project_root / "alembic" / "versions",
project_root / "app" / "modules" / "billing" / "migrations" / "versions",
project_root / "app" / "modules" / "cart" / "migrations" / "versions",
project_root / "app" / "modules" / "catalog" / "migrations" / "versions",
project_root / "app" / "modules" / "cms" / "migrations" / "versions",
project_root / "app" / "modules" / "customers" / "migrations" / "versions",
project_root / "app" / "modules" / "dev_tools" / "migrations" / "versions",
project_root / "app" / "modules" / "inventory" / "migrations" / "versions",
project_root / "app" / "modules" / "loyalty" / "migrations" / "versions",
project_root / "app" / "modules" / "marketplace" / "migrations" / "versions",
project_root / "app" / "modules" / "messaging" / "migrations" / "versions",
project_root / "app" / "modules" / "orders" / "migrations" / "versions",
]
def check_prerequisites():
"""Verify PostgreSQL is configured."""
@@ -53,28 +69,42 @@ def check_prerequisites():
def backup_migrations():
"""Backup existing migrations."""
"""Backup existing migrations from all version locations."""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_dir = project_root / "alembic" / f"versions_backup_{timestamp}"
backup_dir.mkdir(parents=True, exist_ok=True)
if not VERSIONS_DIR.exists():
print("No existing migrations to backup")
total_backed_up = 0
for versions_dir in MODULE_MIGRATION_DIRS:
if not versions_dir.exists():
continue
migration_files = [f for f in versions_dir.glob("*.py") if f.name != "__init__.py"]
if not migration_files:
continue
# Create a subdirectory in backup matching the source path
rel_path = versions_dir.relative_to(project_root)
target_dir = backup_dir / str(rel_path).replace("/", "_")
target_dir.mkdir(parents=True, exist_ok=True)
for f in migration_files:
shutil.copy2(f, target_dir / f.name)
total_backed_up += 1
# Remove migration files from source (keep __init__.py)
for f in migration_files:
f.unlink()
if total_backed_up > 0:
print(f"Backed up {total_backed_up} migrations to {backup_dir.name}/")
return backup_dir
else:
print("No migration files found to backup")
backup_dir.rmdir()
return None
migration_files = list(VERSIONS_DIR.glob("*.py"))
if not migration_files:
print("No migration files found")
return None
print(f"Backing up {len(migration_files)} migrations to {backup_dir.name}/")
shutil.copytree(VERSIONS_DIR, backup_dir)
# Clear versions directory (keep __pycache__ if exists)
for f in VERSIONS_DIR.glob("*.py"):
f.unlink()
return backup_dir
def create_fresh_migration():
"""Generate fresh initial migration from models."""
@@ -116,7 +146,6 @@ def clean_migration_file(migration_path: Path):
# Remove batch_alter_table references (not needed for PostgreSQL)
if "batch_alter_table" in content:
print("Note: Migration contains batch_alter_table - this is not needed for PostgreSQL")
# We don't auto-remove as it might be intentional
print(f"Review migration at: {migration_path}")
@@ -178,14 +207,14 @@ def main():
print(f"Backup location: {backup_dir}")
print("")
print("Next steps:")
print("1. Review the new migration file")
print("2. On a fresh database, run: make migrate-up")
print("1. Review the new migration files")
print("2. On a fresh database, run: make db-reset")
print("3. Verify all tables are created correctly")
print("4. If satisfied, delete the backup directory")
print("")
print("To restore from backup:")
print(f" rm -rf alembic/versions/*.py")
print(f" cp -r {backup_dir}/* alembic/versions/")
if backup_dir:
print("To restore from backup:")
print(f" Check {backup_dir}/ for backed up migration files")
if __name__ == "__main__":