From 3987f5ebbef6ff825bbb2119f4d79119eb0749eb Mon Sep 17 00:00:00 2001 From: Teri-anric <2005ahi2005@gmail.com> Date: Tue, 28 Jan 2025 15:44:06 +0200 Subject: [PATCH 1/5] add limit prams to possiton history endpoint --- web_app/api/position.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/web_app/api/position.py b/web_app/api/position.py index ec4f68ac..4d97de50 100644 --- a/web_app/api/position.py +++ b/web_app/api/position.py @@ -6,7 +6,7 @@ from typing import Optional from uuid import UUID -from fastapi import APIRouter, HTTPException, Request +from fastapi import APIRouter, HTTPException, Query, Request from web_app.api.serializers.position import ( AddPositionDepositData, @@ -315,11 +315,12 @@ async def add_extra_deposit(position_id: UUID, data: AddPositionDepositData): summary="Get all positions for a user", response_description="Returns paginated list of positions for the given wallet ID", ) -async def get_user_positions(wallet_id: str, start: Optional[int] = None) -> list: +async def get_user_positions(wallet_id: str, start: Optional[int] = None, limit: int = Query(PAGINATION_STEP, ge=1, le=100)) -> list: """ Get all positions for a specific user by their wallet ID. :param wallet_id: The wallet ID of the user :param start: Optional starting index for pagination (0-based). If not provided, defaults to 0 + :param limit: Optional number of records to return. min 1, max 100. If not provided, defaults to PAGINATION_STEP :return: UserPositionsListResponse containing paginated list of positions :raises: HTTPException: If wallet ID is empty or invalid """ @@ -329,7 +330,7 @@ async def get_user_positions(wallet_id: str, start: Optional[int] = None) -> lis start_index = max(0, start) if start is not None else 0 positions = position_db_connector.get_all_positions_by_wallet_id( - wallet_id, start_index, PAGINATION_STEP + wallet_id, start_index, limit ) return positions From e36ac360930ff7680ac22e9749ff40e5e9a175b5 Mon Sep 17 00:00:00 2001 From: Teri-anric <2005ahi2005@gmail.com> Date: Tue, 28 Jan 2025 15:48:04 +0200 Subject: [PATCH 2/5] fix pylint --- web_app/api/position.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/web_app/api/position.py b/web_app/api/position.py index 4d97de50..0c58a06f 100644 --- a/web_app/api/position.py +++ b/web_app/api/position.py @@ -315,12 +315,17 @@ async def add_extra_deposit(position_id: UUID, data: AddPositionDepositData): summary="Get all positions for a user", response_description="Returns paginated list of positions for the given wallet ID", ) -async def get_user_positions(wallet_id: str, start: Optional[int] = None, limit: int = Query(PAGINATION_STEP, ge=1, le=100)) -> list: +async def get_user_positions( + wallet_id: str, + start: Optional[int] = None, + limit: int = Query(PAGINATION_STEP, ge=1, le=100), +) -> list: """ Get all positions for a specific user by their wallet ID. :param wallet_id: The wallet ID of the user :param start: Optional starting index for pagination (0-based). If not provided, defaults to 0 - :param limit: Optional number of records to return. min 1, max 100. If not provided, defaults to PAGINATION_STEP + :param limit: Optional number of records to return. min 1, max 100. + If not provided, defaults to PAGINATION_STEP :return: UserPositionsListResponse containing paginated list of positions :raises: HTTPException: If wallet ID is empty or invalid """ @@ -349,8 +354,7 @@ async def get_list_of_deposited_tokens(position_id: UUID): :return Dict containing main position and extra positions """ main_position = position_db_connector.get_position_by_id(position_id) - extra_deposits = position_db_connector.get_extra_deposits_by_position_id(position_id) - return { - "main": main_position, - "extra_deposits": extra_deposits - } + extra_deposits = position_db_connector.get_extra_deposits_by_position_id( + position_id + ) + return {"main": main_position, "extra_deposits": extra_deposits} From ee753c2b7e5eedc8a1b97c1209610a85faf6885b Mon Sep 17 00:00:00 2001 From: Teri-anric <2005ahi2005@gmail.com> Date: Tue, 28 Jan 2025 18:54:43 +0200 Subject: [PATCH 3/5] add pagination and total count in user positions endpoint --- web_app/api/position.py | 32 ++++++++++++++++------------- web_app/api/serializers/position.py | 13 ++++++++++++ web_app/db/crud/position.py | 26 ++++++++++++++++++++++- 3 files changed, 56 insertions(+), 15 deletions(-) diff --git a/web_app/api/position.py b/web_app/api/position.py index 0c58a06f..f466359e 100644 --- a/web_app/api/position.py +++ b/web_app/api/position.py @@ -12,8 +12,8 @@ AddPositionDepositData, PositionFormData, TokenMultiplierResponse, - UserPositionResponse, UserPositionExtraDepositsResponse, + UserPositionHistoryResponse, ) from web_app.api.serializers.transaction import ( LoopLiquidityData, @@ -311,33 +311,37 @@ async def add_extra_deposit(position_id: UUID, data: AddPositionDepositData): @router.get( "/api/user-positions/{wallet_id}", tags=["Position Operations"], - response_model=list[UserPositionResponse], + response_model=UserPositionHistoryResponse, summary="Get all positions for a user", - response_description="Returns paginated list of positions for the given wallet ID", + response_description="Returns paginated of positions for the given wallet ID", ) async def get_user_positions( wallet_id: str, - start: Optional[int] = None, + start: int = Query(0, ge=0), limit: int = Query(PAGINATION_STEP, ge=1, le=100), -) -> list: +) -> UserPositionHistoryResponse: """ Get all positions for a specific user by their wallet ID. - :param wallet_id: The wallet ID of the user - :param start: Optional starting index for pagination (0-based). If not provided, defaults to 0 - :param limit: Optional number of records to return. min 1, max 100. - If not provided, defaults to PAGINATION_STEP - :return: UserPositionsListResponse containing paginated list of positions + + :param wallet_id: Wallet ID of the user + :param start: Starting index for pagination (default: 0) + :param limit: Number of items per page (default: 10 from PAGINATION_STEP variable) + + :return: UserPositionHistoryResponse with positions and total count :raises: HTTPException: If wallet ID is empty or invalid """ if not wallet_id: raise HTTPException(status_code=400, detail="Wallet ID is required") - start_index = max(0, start) if start is not None else 0 - positions = position_db_connector.get_all_positions_by_wallet_id( - wallet_id, start_index, limit + wallet_id, start=start, limit=limit + ) + total_positions = position_db_connector.get_count_positions_by_wallet_id(wallet_id) + + return UserPositionHistoryResponse( + positions=positions, + total_count=total_positions ) - return positions @router.get( diff --git a/web_app/api/serializers/position.py b/web_app/api/serializers/position.py index 13a516e7..69392ad2 100644 --- a/web_app/api/serializers/position.py +++ b/web_app/api/serializers/position.py @@ -113,3 +113,16 @@ class UserPositionExtraDepositsResponse(BaseModel): """ main: UserPositionResponse extra_deposits: list[UserExtraDeposit] + + +class UserPositionHistoryResponse(BaseModel): + """ + Response model for user position history with pagination. + + ### Attributes: + - **positions**: List of user positions + - **total_count**: Total number of positions for pagination + """ + + positions: List[UserPositionResponse] = [] + total_count: int = 0 diff --git a/web_app/db/crud/position.py b/web_app/db/crud/position.py index 9304ab45..e4c384d8 100644 --- a/web_app/db/crud/position.py +++ b/web_app/db/crud/position.py @@ -106,7 +106,7 @@ def get_positions_by_wallet_id( def get_all_positions_by_wallet_id( self, wallet_id: str, start: int, limit: int - ) -> list: + ) -> list[dict]: """ Retrieves paginated positions for a user by their wallet ID and returns them as a list of dictionaries. @@ -138,6 +138,30 @@ def get_all_positions_by_wallet_id( except SQLAlchemyError as e: logger.error(f"Failed to retrieve positions: {str(e)}") return [] + + def get_count_positions_by_wallet_id(self, wallet_id: str) -> int: + """ + Counts total number of positions for a user. + + :param wallet_id: Wallet ID of the user + :return: Total number of positions + """ + with self.Session() as db: + user = self._get_user_by_wallet_id(wallet_id) + if not user: + return 0 + + try: + total_positions = ( + db.query(func.count(Position.id)) + .filter(Position.user_id == user.id) + .scalar() + ) + return total_positions or 0 + + except SQLAlchemyError as e: + logger.error(f"Failed to count user positions: {str(e)}") + return 0 def has_opened_position(self, wallet_id: str) -> bool: """ From 3d5adb38761fb3a0371233d9c1c6dac9803cdcc8 Mon Sep 17 00:00:00 2001 From: Teri-anric <2005ahi2005@gmail.com> Date: Tue, 28 Jan 2025 19:06:13 +0200 Subject: [PATCH 4/5] fix test for get user positions --- web_app/tests/test_positions.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/web_app/tests/test_positions.py b/web_app/tests/test_positions.py index 0ed092c5..d0f8cd63 100644 --- a/web_app/tests/test_positions.py +++ b/web_app/tests/test_positions.py @@ -478,9 +478,10 @@ async def test_get_user_positions_success(client: TestClient) -> None: assert response.status_code == 200 data = response.json() - assert len(data) == len(mock_positions) - assert data[0]["token_symbol"] == mock_positions[0]["token_symbol"] - assert data[0]["amount"] == mock_positions[0]["amount"] + assert len(data["positions"]) == len(mock_positions) + assert data["total_count"] == len(mock_positions) + assert data["positions"][0]["token_symbol"] == mock_positions[0]["token_symbol"] + assert data["positions"][0]["amount"] == mock_positions[0]["amount"] @pytest.mark.asyncio @@ -506,7 +507,7 @@ async def test_get_user_positions_no_positions(client: AsyncClient) -> None: assert response.status_code == 200 data = response.json() - assert data == [] + assert data == {"positions": [], "total_count": 0} @pytest.mark.parametrize( From 9bc8b85e173f459c82f23f8efc586991cdb21287 Mon Sep 17 00:00:00 2001 From: Teri-anric <2005ahi2005@gmail.com> Date: Tue, 28 Jan 2025 19:14:41 +0200 Subject: [PATCH 5/5] Update test_get_user_positions to mock total count --- web_app/tests/test_positions.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/web_app/tests/test_positions.py b/web_app/tests/test_positions.py index d0f8cd63..7bcc8a2f 100644 --- a/web_app/tests/test_positions.py +++ b/web_app/tests/test_positions.py @@ -469,17 +469,22 @@ async def test_get_user_positions_success(client: TestClient) -> None: "is_liquidated": False, } ] + mock_total_count = len(mock_positions) with patch( "web_app.db.crud.PositionDBConnector.get_all_positions_by_wallet_id" - ) as mock_get_positions: + ) as mock_get_positions, patch( + "web_app.db.crud.PositionDBConnector.get_count_positions_by_wallet_id" + ) as mock_get_count_positions: mock_get_positions.return_value = mock_positions + mock_get_count_positions.return_value = mock_total_count + response = client.get(f"/api/user-positions/{wallet_id}") assert response.status_code == 200 data = response.json() assert len(data["positions"]) == len(mock_positions) - assert data["total_count"] == len(mock_positions) + assert data["total_count"] == mock_total_count assert data["positions"][0]["token_symbol"] == mock_positions[0]["token_symbol"] assert data["positions"][0]["amount"] == mock_positions[0]["amount"]