From 723bbbc6950c730da446a1a6992a6d53ad33bce3 Mon Sep 17 00:00:00 2001 From: Vadym Samoilenko Date: Mon, 27 Apr 2026 16:05:17 +0100 Subject: [PATCH] fix: add project_manager migration + add migration step to full-deploy.sh - New migration updates MongoDB users collection validator to accept project_manager role and pm_client_ids field - full-deploy.sh was missing the run_migrations step entirely; added it after rebuild_containers so new role/field validators apply on every deploy Co-Authored-By: Claude Sonnet 4.6 --- ...6-04-27-000000_add_project_manager_role.py | 142 ++++++++++++++++++ scripts/full-deploy.sh | 18 +++ 2 files changed, 160 insertions(+) create mode 100644 backend/app/migrations/scripts/migration_2026-04-27-000000_add_project_manager_role.py diff --git a/backend/app/migrations/scripts/migration_2026-04-27-000000_add_project_manager_role.py b/backend/app/migrations/scripts/migration_2026-04-27-000000_add_project_manager_role.py new file mode 100644 index 0000000..c01f22c --- /dev/null +++ b/backend/app/migrations/scripts/migration_2026-04-27-000000_add_project_manager_role.py @@ -0,0 +1,142 @@ +"""Add project_manager role to user collection schema validator.""" + +from app.migrations.migrator import Migration + + +class Migration(Migration): + """Update MongoDB schema validator to support project_manager role.""" + + def __init__(self): + super().__init__() + self.version = "2026-04-27-000000" + self.description = "Add project_manager role to user schema validator" + + async def up(self) -> None: + """Update the users collection validator.""" + + validator = { + "$jsonSchema": { + "bsonType": "object", + "required": ["email", "full_name", "role", "auth_provider", "is_active"], + "properties": { + "email": { + "bsonType": "string", + "pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", + "description": "Must be a valid email address" + }, + "hashed_password": { + "bsonType": ["string", "null"], + "description": "Hashed password (null for Microsoft users)" + }, + "full_name": { + "bsonType": "string", + "minLength": 1, + "description": "User's full name" + }, + "role": { + "enum": ["client", "reviewer", "linguist", "production", "project_manager", "admin"], + "description": "User role" + }, + "auth_provider": { + "enum": ["local", "microsoft"], + "description": "Authentication provider" + }, + "is_active": { + "bsonType": "bool", + "description": "Whether user account is active" + }, + "pm_client_ids": { + "bsonType": ["array"], + "items": {"bsonType": "string"}, + "description": "Client IDs where user is assigned as Project Manager" + }, + "created_at": { + "bsonType": "date", + "description": "Account creation timestamp" + }, + "updated_at": { + "bsonType": "date", + "description": "Last update timestamp" + } + } + } + } + + try: + await self.db.command({ + "collMod": "users", + "validator": validator, + "validationLevel": "moderate", + "validationAction": "error" + }) + print(f"✅ Updated users collection validator") + except Exception as e: + print(f"⚠️ Could not update validator: {e}") + try: + await self.db.create_collection( + "users", + validator=validator, + validationLevel="moderate", + validationAction="error" + ) + print(f"✅ Created users collection with validator") + except Exception as e2: + print(f"⚠️ Could not create collection: {e2}") + + print(f"✅ Applied migration {self.version}: {self.description}") + + async def down(self) -> None: + """Revert to validator without project_manager role.""" + + validator = { + "$jsonSchema": { + "bsonType": "object", + "required": ["email", "full_name", "role", "auth_provider", "is_active"], + "properties": { + "email": { + "bsonType": "string", + "pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", + "description": "Must be a valid email address" + }, + "hashed_password": { + "bsonType": ["string", "null"], + "description": "Hashed password (null for Microsoft users)" + }, + "full_name": { + "bsonType": "string", + "minLength": 1, + "description": "User's full name" + }, + "role": { + "enum": ["client", "reviewer", "linguist", "production", "admin"], + "description": "User role" + }, + "auth_provider": { + "enum": ["local", "microsoft"], + "description": "Authentication provider" + }, + "is_active": { + "bsonType": "bool", + "description": "Whether user account is active" + }, + "created_at": { + "bsonType": "date", + "description": "Account creation timestamp" + }, + "updated_at": { + "bsonType": "date", + "description": "Last update timestamp" + } + } + } + } + + await self.db.command({ + "collMod": "users", + "validator": validator, + "validationLevel": "moderate", + "validationAction": "error" + }) + + print(f"⚠️ Rolled back migration {self.version}: {self.description}") + print(f"⚠️ WARNING: project_manager role users will fail validation!") diff --git a/scripts/full-deploy.sh b/scripts/full-deploy.sh index 806b478..1d73853 100755 --- a/scripts/full-deploy.sh +++ b/scripts/full-deploy.sh @@ -246,6 +246,23 @@ build_and_deploy_frontend() { echo "" } +# ============================================================================= +# Run Database Migrations +# ============================================================================= + +run_migrations() { + print_header "Running Database Migrations" + + print_info "Checking migration status..." + docker compose $COMPOSE_FILES exec -T api python migrate.py status + + print_info "Applying pending migrations..." + docker compose $COMPOSE_FILES exec -T api python migrate.py up + print_success "Migrations completed" + + echo "" +} + # ============================================================================= # Display Deployment Summary # ============================================================================= @@ -335,6 +352,7 @@ main() { preflight_checks verify_code_updated rebuild_containers + run_migrations build_and_deploy_frontend display_summary fi