diff --git a/src/web/controllers/rank_controller.py b/src/web/controllers/rank_controller.py index 9634179..0aad557 100644 --- a/src/web/controllers/rank_controller.py +++ b/src/web/controllers/rank_controller.py @@ -12,7 +12,7 @@ from ...core.models.user import User from ...core.services.score_service import ScoreService from ..auth import get_current_user -from ..mappers import map_user_domain_list_to_response +from ..mappers import map_user_domain_list_to_ranking_response from ..schemas import GlobalRankingResponse, UserRankingResponse router = APIRouter(prefix="/rank", tags=["ranking"]) @@ -64,9 +64,9 @@ async def get_global_ranking( score_service: Injected score service Returns: - List of all users ordered by score + List of all users ordered by score (names and scores only, no emails) """ users = await score_service.get_global_ranking() - user_responses = map_user_domain_list_to_response(users) + global_ranking_user_responses = map_user_domain_list_to_ranking_response(users) - return GlobalRankingResponse(users=user_responses) + return GlobalRankingResponse(users=global_ranking_user_responses) diff --git a/src/web/mappers.py b/src/web/mappers.py index 54526d6..98f760d 100644 --- a/src/web/mappers.py +++ b/src/web/mappers.py @@ -18,6 +18,7 @@ BusRouteResponseSchema, CoordinateSchema, HistoryResponse, + RankingUserResponse, RouteIdentifierSchema, RouteShapeResponse, TripHistoryEntry, @@ -57,6 +58,37 @@ def map_user_domain_list_to_response(users: list[User]) -> list[UserResponse]: return [map_user_domain_to_response(user) for user in users] +def map_user_domain_to_ranking_response(user: User) -> RankingUserResponse: + """ + Map a User domain model to a RankingUserResponse schema (excludes email). + + Args: + user: User domain model + + Returns: + RankingUserResponse schema + """ + return RankingUserResponse( + name=user.name, + score=user.score, + ) + + +def map_user_domain_list_to_ranking_response( + users: list[User], +) -> list[RankingUserResponse]: + """ + Map a list of User domain models to RankingUserResponse schemas (excludes email). + + Args: + users: List of User domain models + + Returns: + List of RankingUserResponse schemas + """ + return [map_user_domain_to_ranking_response(user) for user in users] + + # ===== Route Mappers ===== diff --git a/src/web/schemas.py b/src/web/schemas.py index e23ff6c..04182ba 100644 --- a/src/web/schemas.py +++ b/src/web/schemas.py @@ -179,6 +179,15 @@ class RouteShapesResponse(BaseModel): # ===== Ranking Schemas ===== +class RankingUserResponse(BaseModel): + """Response schema for user information in rankings (excludes email).""" + + name: str + score: int + + model_config = {"from_attributes": True} + + class UserRankingResponse(BaseModel): position: int = Field(..., description="User's rank position") @@ -186,7 +195,7 @@ class UserRankingResponse(BaseModel): class GlobalRankingResponse(BaseModel): """Response schema for global ranking.""" - users: list[UserResponse] = Field(..., description="List of users by rank") + users: list[RankingUserResponse] = Field(..., description="List of users by rank") # ===== History Schemas ===== diff --git a/tests/integration/test_ranking.py b/tests/integration/test_ranking.py index 5fde5d2..5280e81 100644 --- a/tests/integration/test_ranking.py +++ b/tests/integration/test_ranking.py @@ -109,9 +109,9 @@ async def test_get_global_ranking_should_work( first_user = data["users"][0] assert "name" in first_user - assert "email" in first_user + assert "email" not in first_user assert "score" in first_user - assert first_user["email"] == "first@example.com" + assert first_user["score"] == 1000 @pytest.mark.asyncio async def test_get_global_ranking_with_single_user( @@ -134,8 +134,8 @@ async def test_get_global_ranking_with_single_user( data = response.json() assert len(data["users"]) == 2 - assert data["users"][0]["email"] == "solo@example.com" assert data["users"][0]["score"] == 500 + assert "email" not in data["users"][0] @pytest.mark.asyncio async def test_get_global_ranking_without_auth_fails(