""" Authentication Service Main Application """ from fastapi import FastAPI from sqlalchemy import text from app.core.config import settings from app.core.database import database_manager from app.api import auth_operations, users, onboarding_progress, consent, data_export, account_deletion, internal_demo, password_reset from shared.service_base import StandardFastAPIService from shared.messaging import UnifiedEventPublisher class AuthService(StandardFastAPIService): """Authentication Service with standardized setup""" async def on_startup(self, app): """Custom startup logic including migration verification and Redis initialization""" self.logger.info("Starting auth service on_startup") await self.verify_migrations() # Initialize Redis if not already done during service creation if not self.redis_initialized: try: from shared.redis_utils import initialize_redis, get_redis_client await initialize_redis(settings.REDIS_URL_WITH_DB, db=settings.REDIS_DB, max_connections=getattr(settings, 'REDIS_MAX_CONNECTIONS', 50)) self.redis_client = await get_redis_client() self.redis_initialized = True self.logger.info("Connected to Redis for token management") except Exception as e: self.logger.error(f"Failed to connect to Redis during startup: {e}") raise await super().on_startup(app) async def on_shutdown(self, app): """Custom shutdown logic for Auth Service""" await super().on_shutdown(app) # Close Redis from shared.redis_utils import close_redis await close_redis() self.logger.info("Redis connection closed") async def verify_migrations(self): """Verify database schema matches the latest migrations.""" try: async with self.database_manager.get_session() as session: # Check if alembic_version table exists result = await session.execute(text(""" SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'alembic_version' ) """)) table_exists = result.scalar() if table_exists: # If table exists, check the version result = await session.execute(text("SELECT version_num FROM alembic_version")) version = result.scalar() self.logger.info(f"Migration verification successful: {version}") else: # If table doesn't exist, migrations might not have run yet # This is OK - the migration job should create it self.logger.warning("alembic_version table does not exist yet - migrations may not have run") except Exception as e: self.logger.warning(f"Migration verification failed (this may be expected during initial setup): {e}") def __init__(self): # Initialize Redis during service creation so it's available when needed try: import asyncio # We need to run the async initialization in a sync context try: # Check if there's already a running event loop loop = asyncio.get_running_loop() # If there is, we'll initialize Redis later in on_startup self.redis_initialized = False self.redis_client = None except RuntimeError: # No event loop running, safe to run the async function import asyncio import nest_asyncio nest_asyncio.apply() # Allow nested event loops async def init_redis(): from shared.redis_utils import initialize_redis, get_redis_client await initialize_redis(settings.REDIS_URL_WITH_DB, db=settings.REDIS_DB, max_connections=getattr(settings, 'REDIS_MAX_CONNECTIONS', 50)) return await get_redis_client() self.redis_client = asyncio.run(init_redis()) self.redis_initialized = True self.logger.info("Connected to Redis for token management") except Exception as e: self.logger.error(f"Failed to initialize Redis during service creation: {e}") self.redis_initialized = False self.redis_client = None # Define expected database tables for health checks auth_expected_tables = [ 'users', 'refresh_tokens', 'user_onboarding_progress', 'user_onboarding_summary', 'login_attempts', 'user_consents', 'consent_history', 'audit_logs' ] # Define custom metrics for auth service auth_custom_metrics = { "registration_total": { "type": "counter", "description": "Total user registrations by status", "labels": ["status"] }, "login_success_total": { "type": "counter", "description": "Total successful user logins" }, "login_failure_total": { "type": "counter", "description": "Total failed user logins by reason", "labels": ["reason"] }, "token_refresh_total": { "type": "counter", "description": "Total token refreshes by status", "labels": ["status"] }, "token_verify_total": { "type": "counter", "description": "Total token verifications by status", "labels": ["status"] }, "logout_total": { "type": "counter", "description": "Total user logouts by status", "labels": ["status"] }, "registration_duration_seconds": { "type": "histogram", "description": "Registration request duration" }, "login_duration_seconds": { "type": "histogram", "description": "Login request duration" }, "token_refresh_duration_seconds": { "type": "histogram", "description": "Token refresh duration" } } super().__init__( service_name="auth-service", app_name="Authentication Service", description="Handles user authentication and authorization for bakery forecasting platform", version="1.0.0", log_level=settings.LOG_LEVEL, api_prefix="", # Empty because RouteBuilder already includes /api/v1 database_manager=database_manager, expected_tables=auth_expected_tables, enable_messaging=True, custom_metrics=auth_custom_metrics ) async def _setup_messaging(self): """Setup messaging for auth service""" from shared.messaging import RabbitMQClient try: self.rabbitmq_client = RabbitMQClient(settings.RABBITMQ_URL, service_name="auth-service") await self.rabbitmq_client.connect() # Create event publisher self.event_publisher = UnifiedEventPublisher(self.rabbitmq_client, "auth-service") self.logger.info("Auth service messaging setup completed") except Exception as e: self.logger.error("Failed to setup auth messaging", error=str(e)) raise async def _cleanup_messaging(self): """Cleanup messaging for auth service""" try: if self.rabbitmq_client: await self.rabbitmq_client.disconnect() self.logger.info("Auth service messaging cleanup completed") except Exception as e: self.logger.error("Error during auth messaging cleanup", error=str(e)) async def on_shutdown(self, app: FastAPI): """Custom shutdown logic for auth service""" self.logger.info("Authentication Service shutdown complete") def get_service_features(self): """Return auth-specific features""" return [ "user_authentication", "token_management", "user_onboarding", "role_based_access", "messaging_integration" ] # Create service instance service = AuthService() # Create FastAPI app with standardized setup app = service.create_app( docs_url="/docs", redoc_url="/redoc" ) # Setup standard endpoints service.setup_standard_endpoints() # Include routers with specific configurations # Note: Routes now use RouteBuilder which includes full paths, so no prefix needed service.add_router(auth_operations.router, tags=["authentication"]) service.add_router(users.router, tags=["users"]) service.add_router(onboarding_progress.router, tags=["onboarding"]) service.add_router(consent.router, tags=["gdpr", "consent"]) service.add_router(data_export.router, tags=["gdpr", "data-export"]) service.add_router(account_deletion.router, tags=["gdpr", "account-deletion"]) service.add_router(internal_demo.router, tags=["internal-demo"]) service.add_router(password_reset.router, tags=["password-reset"])