# shared/clients/auth_client.py """ Auth Service Client for Inter-Service Communication Provides methods to interact with the authentication/onboarding service """ from typing import Optional, Dict, Any, List import structlog from shared.clients.base_service_client import BaseServiceClient from shared.config.base import BaseServiceSettings logger = structlog.get_logger() class AuthServiceClient(BaseServiceClient): """Client for interacting with the Auth Service""" def __init__(self, config: BaseServiceSettings): super().__init__("auth", config) def get_service_base_path(self) -> str: """Return the base path for auth service APIs""" return "/api/v1/auth" async def get_user_onboarding_progress(self, user_id: str) -> Optional[Dict[str, Any]]: """ Get user's onboarding progress including step data Args: user_id: User ID to fetch progress for Returns: Dict with user progress including steps with data, or None if failed """ try: # Use the service endpoint that accepts user_id as parameter result = await self.get(f"/users/{user_id}/onboarding/progress") if result: logger.info("Retrieved user onboarding progress", user_id=user_id, current_step=result.get("current_step")) return result else: logger.warning("No onboarding progress found", user_id=user_id) return None except Exception as e: logger.error("Failed to get user onboarding progress", user_id=user_id, error=str(e)) return None async def get_user_step_data(self, user_id: str, step_name: str) -> Optional[Dict[str, Any]]: """ Get data for a specific onboarding step Args: user_id: User ID step_name: Name of the step (e.g., "user_registered") Returns: Step data dictionary or None if not found """ try: progress = await self.get_user_onboarding_progress(user_id) if not progress: logger.warning("No progress data returned", user_id=user_id) return None logger.debug("Retrieved progress data", user_id=user_id, steps_count=len(progress.get("steps", [])), current_step=progress.get("current_step")) # Find the specific step for step in progress.get("steps", []): if step.get("step_name") == step_name: step_data = step.get("data", {}) logger.info("Found step data", user_id=user_id, step_name=step_name, data_keys=list(step_data.keys()) if step_data else [], has_subscription_plan="subscription_plan" in step_data) return step_data logger.warning("Step not found in progress", user_id=user_id, step_name=step_name, available_steps=[s.get("step_name") for s in progress.get("steps", [])]) return None except Exception as e: logger.error("Failed to get step data", user_id=user_id, step_name=step_name, error=str(e)) return None async def get_subscription_plan_from_registration(self, user_id: str) -> str: """ Get the subscription plan selected during user registration Args: user_id: User ID Returns: Plan name (e.g., "starter", "professional", "enterprise") or "starter" as default """ try: step_data = await self.get_user_step_data(user_id, "user_registered") if step_data and "subscription_plan" in step_data: plan = step_data["subscription_plan"] logger.info("Retrieved subscription plan from registration", user_id=user_id, plan=plan) return plan else: logger.info("No subscription plan in registration data, using default", user_id=user_id, default_plan="starter") return "starter" except Exception as e: logger.warning("Failed to retrieve subscription plan, using default", user_id=user_id, error=str(e), default_plan="starter") return "starter" async def create_user_by_owner(self, user_data: Dict[str, Any]) -> Dict[str, Any]: """ Create a new user account via the auth service (owner/admin only - pilot phase). This method calls the auth service endpoint that allows tenant owners to directly create users with passwords during the pilot phase. Args: user_data: Dictionary containing: - email: User email (required) - full_name: Full name (required) - password: Password (required) - phone: Phone number (optional) - role: User role (optional, default: "user") - language: Language preference (optional, default: "es") - timezone: Timezone (optional, default: "Europe/Madrid") Returns: Dict with created user data including user ID Raises: Exception if user creation fails """ try: logger.info( "Creating user via auth service", email=user_data.get("email"), role=user_data.get("role", "user") ) result = await self.post("/users/create-by-owner", user_data) if result and result.get("id"): logger.info( "User created successfully via auth service", user_id=result.get("id"), email=result.get("email") ) return result else: logger.error("User creation returned no user ID") raise Exception("User creation failed: No user ID returned") except Exception as e: logger.error( "Failed to create user via auth service", email=user_data.get("email"), error=str(e) ) raise