From 53e5916c6c07e2bbc2e6e2acc8afecb31066819e Mon Sep 17 00:00:00 2001 From: Samir Boulahtit Date: Sun, 19 Oct 2025 15:53:56 +0200 Subject: [PATCH] Admin user creation moved to scripts --- app/core/lifespan.py | 33 -------- scripts/create_admin.py | 167 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+), 33 deletions(-) create mode 100644 scripts/create_admin.py diff --git a/app/core/lifespan.py b/app/core/lifespan.py index a1419d43..6c0731c6 100644 --- a/app/core/lifespan.py +++ b/app/core/lifespan.py @@ -31,32 +31,6 @@ async def lifespan(app: FastAPI): # === STARTUP === app_logger = setup_logging() app_logger.info("Starting up ecommerce API") - - # === REMOVED: Database table creation === - # Base.metadata.create_all(bind=engine) # Removed - handled by Alembic - # create_indexes() # Removed - handled by Alembic - - # === KEPT: Application-level initialization === - - # Create default admin user (after migrations have run) - db = SessionLocal() - try: - auth_manager.create_default_admin_user(db) - logger.info("Default admin user initialization completed") - except Exception as e: - logger.error(f"Failed to create default admin user: {e}") - # In development, this might fail if tables don't exist yet - # That's OK - migrations should be run separately - finally: - db.close() - - # Add any other application-level initialization here - # Examples: - # - Load configuration - # - Initialize caches - # - Set up external service connections - # - Load initial data (AFTER migrations) - logger.info("[OK] Application startup completed") yield @@ -66,13 +40,6 @@ async def lifespan(app: FastAPI): # Add cleanup tasks here if needed -# === REMOVED FUNCTION === -# def create_indexes(): -# """Create database indexes.""" -# # This is now handled by Alembic migrations -# pass - - # === NEW HELPER FUNCTION === def check_database_ready(): """Check if database is ready (migrations have been run).""" diff --git a/scripts/create_admin.py b/scripts/create_admin.py new file mode 100644 index 00000000..8a8a721f --- /dev/null +++ b/scripts/create_admin.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +""" +Create default admin user for the platform. + +Usage: + python scripts/create_admin.py + +This script: +- Creates a default admin user if one doesn't exist +- Can be run multiple times safely (idempotent) +- Should be run AFTER database migrations +""" + +import sys +from pathlib import Path + +# Add project root to path +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root)) + +from sqlalchemy.orm import Session +from sqlalchemy import select + +from app.core.database import SessionLocal, engine +from models.database.user import User +from middleware.auth import AuthManager + +# Default admin credentials +DEFAULT_ADMIN_EMAIL = "admin@platform.com" +DEFAULT_ADMIN_USERNAME = "admin" +DEFAULT_ADMIN_PASSWORD = "admin123" # Change this in production! + + +def create_admin_user(db: Session) -> bool: + """ + Create default admin user if it doesn't exist. + + Args: + db: Database session + + Returns: + bool: True if user was created, False if already exists + """ + auth_manager = AuthManager() + + # Check if admin user already exists + existing_admin = db.execute( + select(User).where(User.username == DEFAULT_ADMIN_USERNAME) + ).scalar_one_or_none() + + if existing_admin: + print(f"â„šī¸ Admin user '{DEFAULT_ADMIN_USERNAME}' already exists") + print(f" Email: {existing_admin.email}") + print(f" Role: {existing_admin.role}") + print(f" Active: {existing_admin.is_active}") + return False + + # Create new admin user + print(f"📝 Creating admin user...") + + admin_user = User( + email=DEFAULT_ADMIN_EMAIL, + username=DEFAULT_ADMIN_USERNAME, + hashed_password=auth_manager.hash_password(DEFAULT_ADMIN_PASSWORD), + role="admin", + is_active=True + ) + + db.add(admin_user) + db.commit() + db.refresh(admin_user) + + print("\n✅ Admin user created successfully!") + print("\n" + "=" * 50) + print(" Admin Credentials:") + print("=" * 50) + print(f" Email: {DEFAULT_ADMIN_EMAIL}") + print(f" Username: {DEFAULT_ADMIN_USERNAME}") + print(f" Password: {DEFAULT_ADMIN_PASSWORD}") + print("=" * 50) + print("\nâš ī¸ IMPORTANT: Change the password after first login!") + print(f" Login at: http://localhost:8000/static/admin/login.html") + print() + + return True + + +def verify_database_ready() -> bool: + """ + Verify that database tables exist. + + Returns: + bool: True if database is ready, False otherwise + """ + try: + # Try to query the users table + with engine.connect() as conn: + from sqlalchemy import text + result = conn.execute( + text("SELECT name FROM sqlite_master WHERE type='table' AND name='users'") + ) + tables = result.fetchall() + return len(tables) > 0 + except Exception as e: + print(f"❌ Error checking database: {e}") + return False + + +def main(): + """Main function to create admin user.""" + + print("\n" + "=" * 50) + print(" Admin User Creation Script") + print("=" * 50 + "\n") + + # Step 1: Verify database is ready + print("1ī¸âƒŖ Checking database...") + + if not verify_database_ready(): + print("\n❌ ERROR: Database not ready!") + print("\n The 'users' table doesn't exist yet.") + print(" Please run database migrations first:") + print() + print(" alembic upgrade head") + print() + print(" Or if using make:") + print(" make migrate-up") + print() + sys.exit(1) + + print(" ✓ Database is ready") + + # Step 2: Create admin user + print("\n2ī¸âƒŖ Creating admin user...") + + db = SessionLocal() + try: + created = create_admin_user(db) + + if created: + print("\n🎉 Setup complete! You can now:") + print(" 1. Start the server: uvicorn main:app --reload") + print(" 2. Login at: http://localhost:8000/static/admin/login.html") + print() + else: + print("\n✓ No changes needed - admin user already exists") + print() + + sys.exit(0) + + except Exception as e: + print(f"\n❌ ERROR: Failed to create admin user") + print(f" {type(e).__name__}: {e}") + print() + print(" Common issues:") + print(" - Database migrations not run") + print(" - Database connection issues") + print(" - Permissions problems") + print() + sys.exit(1) + + finally: + db.close() + + +if __name__ == "__main__": + main()