From 92169d047b8695c9801bb41bab8acc6eff0528b8 Mon Sep 17 00:00:00 2001 From: michael Date: Fri, 10 Oct 2025 10:55:54 -0500 Subject: [PATCH] added scheme validator --- ...-10-000001_update_user_schema_validator.py | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 backend/app/migrations/scripts/migration_2025-01-10-000001_update_user_schema_validator.py diff --git a/backend/app/migrations/scripts/migration_2025-01-10-000001_update_user_schema_validator.py b/backend/app/migrations/scripts/migration_2025-01-10-000001_update_user_schema_validator.py new file mode 100644 index 0000000..561f94d --- /dev/null +++ b/backend/app/migrations/scripts/migration_2025-01-10-000001_update_user_schema_validator.py @@ -0,0 +1,139 @@ +"""Update user collection schema validator for PRODUCTION role and nullable passwords.""" + +from app.migrations.migrator import Migration + + +class Migration(Migration): + """Update MongoDB schema validator to support production role and Microsoft users.""" + + def __init__(self): + super().__init__() + self.version = "2025-01-10-000001" + self.description = "Update user schema validator for production role and nullable passwords" + + async def up(self) -> None: + """Update the users collection validator.""" + + # Get current validator (if any) + collections_info = await self.db.command("listCollections", filter={"name": "users"}) + current_validator = None + + for collection in collections_info.get("cursor", {}).get("firstBatch", []): + if "options" in collection and "validator" in collection["options"]: + current_validator = collection["options"]["validator"] + print(f"Found existing validator: {current_validator}") + + # Define updated schema 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"], # Allow null for Microsoft users + "description": "Hashed password (null for Microsoft users)" + }, + "full_name": { + "bsonType": "string", + "minLength": 1, + "description": "User's full name" + }, + "role": { + "enum": ["client", "reviewer", "production", "admin"], # Added production + "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" + } + } + } + } + + # Update the collection validator + try: + await self.db.command({ + "collMod": "users", + "validator": validator, + "validationLevel": "moderate", # moderate = only validate on insert/update, not existing docs + "validationAction": "error" # error = reject invalid documents + }) + print(f"✅ Updated users collection validator") + except Exception as e: + print(f"⚠️ Could not update validator: {e}") + # Try creating the collection if it doesn't exist + 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 previous validator (client, reviewer, admin only).""" + + # Define old schema validator (without production) + validator = { + "$jsonSchema": { + "bsonType": "object", + "required": ["email", "hashed_password", "full_name", "role", "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", # Back to required string + "description": "Hashed password" + }, + "full_name": { + "bsonType": "string", + "minLength": 1, + "description": "User's full name" + }, + "role": { + "enum": ["client", "reviewer", "admin"], # Removed production + "description": "User role" + }, + "is_active": { + "bsonType": "bool", + "description": "Whether user account is active" + } + } + } + } + + # Update the collection validator + await self.db.command({ + "collMod": "users", + "validator": validator, + "validationLevel": "moderate", + "validationAction": "error" + }) + + print(f"⚠️ Rolled back migration {self.version}: {self.description}") + print(f"⚠️ WARNING: Production role users will fail validation!")