""" User management API routes """ from fastapi import APIRouter, Depends, HTTPException, status, BackgroundTasks from sqlalchemy.ext.asyncio import AsyncSession from typing import Dict, Any import structlog import uuid from app.core.database import get_db from app.schemas.auth import UserResponse, PasswordChange from app.schemas.users import UserUpdate from app.services.user_service import UserService from app.models.users import User from sqlalchemy.ext.asyncio import AsyncSession from app.services.admin_delete import AdminUserDeleteService # Import unified authentication from shared library from shared.auth.decorators import ( get_current_user_dep, require_admin_role ) logger = structlog.get_logger() router = APIRouter(tags=["users"]) @router.get("/me", response_model=UserResponse) async def get_current_user_info( current_user: Dict[str, Any] = Depends(get_current_user_dep), db: AsyncSession = Depends(get_db) ): """Get current user information""" try: # Handle both User object (direct auth) and dict (from gateway headers) if isinstance(current_user, dict): # Coming from gateway headers - need to fetch user from DB user_id = current_user.get("user_id") if not user_id: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid user context" ) # Fetch full user from database from sqlalchemy import select from app.models.users import User result = await db.execute(select(User).where(User.id == user_id)) user = result.scalar_one_or_none() if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found" ) return UserResponse( id=str(user.id), email=user.email, full_name=user.full_name, is_active=user.is_active, is_verified=user.is_verified, phone=user.phone, language=user.language, timezone=user.timezone, created_at=user.created_at, last_login=user.last_login ) else: # Direct User object (when called directly) return UserResponse( id=str(current_user.id), email=current_user.email, full_name=current_user.full_name, is_active=current_user.is_active, is_verified=current_user.is_verified, phone=current_user.phone, language=current_user.language, timezone=current_user.timezone, created_at=current_user.created_at, last_login=current_user.last_login ) except Exception as e: logger.error(f"Get current user error: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to get user information" ) @router.put("/me", response_model=UserResponse) async def update_current_user( user_update: UserUpdate, current_user: Dict[str, Any] = Depends(get_current_user_dep), db: AsyncSession = Depends(get_db) ): """Update current user information""" try: updated_user = await UserService.update_user(current_user.id, user_update, db) return UserResponse( id=str(updated_user.id), email=updated_user.email, full_name=updated_user.full_name, is_active=updated_user.is_active, is_verified=updated_user.is_verified, phone=updated_user.phone, language=updated_user.language, timezone=updated_user.timezone, created_at=updated_user.created_at, last_login=updated_user.last_login ) except HTTPException: raise except Exception as e: logger.error(f"Update user error: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to update user" ) @router.delete("/delete/users/{user_id}") async def delete_admin_user( user_id: str, background_tasks: BackgroundTasks, current_user = Depends(get_current_user_dep), _admin_check = Depends(require_admin_role), db: AsyncSession = Depends(get_db) ): """ Delete an admin user and all associated data across all services. This operation will: 1. Cancel any active training jobs for user's tenants 2. Delete all trained models and artifacts 3. Delete all forecasts and predictions 4. Delete notification preferences and logs 5. Handle tenant ownership (transfer or delete) 6. Delete user account and authentication data **Warning: This operation is irreversible!** """ # Validate user_id format try: uuid.UUID(user_id) except ValueError: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid user ID format" ) # Prevent self-deletion if user_id == current_user.id: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot delete your own account" ) # Initialize deletion service deletion_service = AdminUserDeleteService(db) # Perform the deletion try: result = await deletion_service.delete_admin_user_complete( user_id=user_id, requesting_user_id=current_user.id ) return { "success": True, "message": f"Admin user {user_id} has been successfully deleted", "deletion_details": result } except HTTPException: raise except Exception as e: logger.error("Unexpected error during user deletion", user_id=user_id, error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="An unexpected error occurred during user deletion" ) @router.get("/delete/users/{user_id}/deletion-preview") async def preview_user_deletion( user_id: str, current_user = Depends(get_current_user_dep), _admin_check = Depends(require_admin_role), db: AsyncSession = Depends(get_db) ): """ Preview what data would be deleted for an admin user. This endpoint provides a dry-run preview of the deletion operation without actually deleting any data. """ try: uuid.UUID(user_id) except ValueError: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid user ID format" ) deletion_service = AdminUserDeleteService(db) # Get user info user_info = await deletion_service._validate_admin_user(user_id) if not user_info: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Admin user {user_id} not found" ) # Get tenant associations tenant_info = await deletion_service._get_user_tenant_info(user_id) # Build preview preview = { "user": user_info, "tenant_associations": tenant_info, "estimated_deletions": { "training_models": "All models for associated tenants", "forecasts": "All forecasts for associated tenants", "notifications": "All user notification data", "tenant_memberships": tenant_info['total_tenants'], "owned_tenants": f"{tenant_info['owned_tenants']} (will be transferred or deleted)" }, "warning": "This operation is irreversible and will permanently delete all associated data" } return preview