From 11465dc3f5adb9cb6ab593386946368acacf2852 Mon Sep 17 00:00:00 2001 From: bernard karaba Date: Wed, 22 Jan 2025 20:42:11 +0300 Subject: [PATCH 1/8] Update logic for dashboard endpoint --- web_app/api/dashboard.py | 10 +++++++++- web_app/api/serializers/dashboard.py | 7 ++++++- web_app/tests/test_dashboard.py | 4 ++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/web_app/api/dashboard.py b/web_app/api/dashboard.py index bae3e438..86b82a27 100644 --- a/web_app/api/dashboard.py +++ b/web_app/api/dashboard.py @@ -25,7 +25,7 @@ async def get_dashboard(wallet_id: str) -> DashboardResponse: """ This endpoint fetches the user's dashboard data, - including balances, multipliers, start dates, and ZkLend position. + including balances, multipliers, start dates, deposit_data and ZkLend position. ### Parameters: - **wallet_id**: User's wallet ID @@ -36,6 +36,8 @@ async def get_dashboard(wallet_id: str) -> DashboardResponse: - **multipliers**: Multipliers applied to each asset (e.g., ETH). - **start_dates**: Start dates for each asset's position. - **zklend_position**: Details of the ZkLend positions. + - **deposit_data**: Deposit data including token and amount. + """ contract_address = position_db_connector.get_contract_address_by_wallet_id( wallet_id @@ -49,6 +51,7 @@ async def get_dashboard(wallet_id: str) -> DashboardResponse: borrowed="0", balance="0", position_id="0", + deposit_data={}, ) if not contract_address: return default_dashboard_response @@ -88,6 +91,10 @@ async def get_dashboard(wallet_id: str) -> DashboardResponse: position_balance, position_multiplier ) token_symbol = first_opened_position["token_symbol"] + deposit_data = { + "token": token_symbol, + "amount": str(Decimal(position_balance) + Decimal(extra_deposit_balance)), + } return DashboardResponse( health_ratio=health_ratio, multipliers={token_symbol: str(position_multiplier)}, @@ -97,4 +104,5 @@ async def get_dashboard(wallet_id: str) -> DashboardResponse: borrowed=str(start_sum * Decimal(tvl)), balance=str(total_position_balance + extra_deposit_balance), position_id=first_opened_position["id"], + deposit_data=deposit_data, ) diff --git a/web_app/api/serializers/dashboard.py b/web_app/api/serializers/dashboard.py index 7332e1b7..25e89417 100644 --- a/web_app/api/serializers/dashboard.py +++ b/web_app/api/serializers/dashboard.py @@ -4,7 +4,7 @@ from datetime import datetime from decimal import Decimal -from typing import Dict +from typing import Dict, Union from pydantic import BaseModel, Field @@ -50,3 +50,8 @@ class DashboardResponse(BaseModel): example="12", description="The position ID.", ) + deposit_data: Dict[str, Union[str, float]] = Field( + ..., + example={"token": "ETH", "amount": "100.0"}, + description="The deposit data including token and amount.", + ) diff --git a/web_app/tests/test_dashboard.py b/web_app/tests/test_dashboard.py index bc6aa645..a8ff6dfb 100644 --- a/web_app/tests/test_dashboard.py +++ b/web_app/tests/test_dashboard.py @@ -147,6 +147,10 @@ async def test_get_dashboard_success(): "balance": str(balance), "health_ratio": "1.2", "position_id": str(id), + "deposit_data": { + "token": "ETH", + "amount": str(mock_position_balance + mock_extra_deposit), + }, } From 5494705fe47b5e9d7f332c21b56cade3c01c5b2d Mon Sep 17 00:00:00 2001 From: ekene leonard Date: Wed, 22 Jan 2025 18:55:11 +0100 Subject: [PATCH 2/8] update files structure for docker compose --- .github/workflows/cd.yml | 2 +- .github/workflows/ci.yml | 8 +-- .github/workflows/integration-tests.yml | 14 ++-- Makefile | 8 +-- README.md | 71 ++++++++++++++----- .../docker-compose.back.yaml | 10 +-- .../docker-compose.dev-windows.yaml | 12 ++-- .../docker-compose.dev.yaml | 16 ++--- .../docker-compose.yaml | 12 ++-- 9 files changed, 96 insertions(+), 57 deletions(-) rename docker-compose.back.yaml => devops/docker-compose.back.yaml (86%) rename docker-compose.dev-windows.yaml => devops/docker-compose.dev-windows.yaml (88%) rename docker-compose.dev.yaml => devops/docker-compose.dev.yaml (83%) rename docker-compose.yaml => devops/docker-compose.yaml (86%) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 976c17ab..5254c216 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -39,5 +39,5 @@ jobs: ssh -p ${{ secrets.SERVER_PORT }} ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }} " mkdir -p ${{ secrets.PROJECT_PATH }} cd ${{ secrets.PROJECT_PATH }} - docker-compose pull && docker-compose up --detach --remove-orphans + docker compose -f devops/docker-compose.yaml pull && docker compose -f devops/docker-compose.yaml up --detach--remove-orphans " diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5239585a..669646e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,16 +38,16 @@ jobs: run: cp .env.dev .env - name: Start services - run: docker compose -f docker-compose.dev.yaml --env-file .env up -d --build + run: docker compose -f devops/docker-compose.dev.yaml --env-file .env up -d --build - name: Run tests run: | - docker compose exec backend poetry run pytest web_app/tests + docker compose -f devops/docker-compose.dev.yaml exec -T backend poetry run pytest web_app/tests - name: Run tests coverage run: | - docker compose exec backend poetry run pytest --cov=web_app/tests --cov-fail-under=90 + docker compose -f devops/docker-compose.dev.yaml exec -T backend poetry run pytest --cov=web_app/tests --cov-fail-under=90 - name: Tear down run: | - docker compose down + docker compose -f devops/docker-compose.dev.yaml down diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 6c0c1f04..c06452da 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -46,7 +46,7 @@ jobs: - name: Build and Start Containers run: | - docker compose -f docker-compose.dev.yaml --env-file .env up -d --build + docker compose -f devops/docker-compose.dev.yaml --env-file .env up -d --build echo "Waiting for containers to be ready..." sleep 30 @@ -65,31 +65,31 @@ jobs: # Check if the container is still running before logging if ! docker ps | grep -q backend; then echo "Backend container is not running!" - docker compose -f docker-compose.dev.yaml logs backend || true + docker compose -f devops/docker-compose.dev.yaml logs backend || true exit 1 fi # Log the backend service status for debugging purposes. - docker compose -f docker-compose.dev.yaml logs backend || true + docker compose -f devops/docker-compose.dev.yaml logs backend || true done - name: Apply Migrations run: | docker exec backend_dev alembic -c web_app/alembic.ini upgrade head || { echo "Migration failed. Showing backend logs:" - docker compose -f docker-compose.dev.yaml logs backend || true + docker compose -f devops/docker-compose.dev.yaml logs backend || true exit 1 } - name: Run Integration Tests with Coverage run: | - docker compose exec backend bash -c "cd /app && python -m pytest web_app/test_integration/ -v" + docker compose -f devops/docker-compose.dev.yaml exec backend bash -c "cd /app && python -m pytest web_app/test_integration/ -v" - name: Clean Up if: always() run: | - docker compose -f docker-compose.dev.yaml logs > docker-logs.txt || true - docker compose -f docker-compose.dev.yaml down -v + docker compose -f devops/docker-compose.dev.yaml logs > docker-logs.txt || true + docker compose -f devops/docker-compose.dev.yaml down -v - name: Upload Docker Logs on Failure if: failure() diff --git a/Makefile b/Makefile index 082e5c0b..fa437482 100644 --- a/Makefile +++ b/Makefile @@ -1,20 +1,20 @@ prod: @echo "Setting up production environment..." - @docker-compose -f docker-compose.yaml up --build + @docker compose -f devops/docker-compose.yaml up --build @echo "Production environment is ready." dev: @echo "Setting up development environment..." - @docker-compose -f docker-compose.dev.yaml up --build + @docker compose -f devops/docker-compose.dev.yaml up --build windows: @echo "Setting up for Windows..." - @docker-compose -f docker-compose.dev-windows.yaml up --build + @docker compose -f devops/docker-compose.dev-windows.yaml up --build @echo "Windows setup completed." back: @echo "Starting backend services..." - @docker-compose -f docker-compose.back.yaml up --build + @docker compose -f devops/docker-compose.back.yaml up --build @echo "Backend services are running." all: diff --git a/README.md b/README.md index 7c5a3dcf..b993545a 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,49 @@ This guide explains how to start the development environment for the project usi ## Prerequisites -- Docker installed on your machine (v19.03+ recommended). -- Docker Compose installed (v1.27+ recommended). +- Docker installed on your machine (2.10+ recommended). +- Docker Compose installed (v2.0+ recommended). - Ensure port **5433** is available for the PostgreSQL container. +### Version Requirements + +1. **Check Docker version:** + ```sh + docker --version + # Should output something like: Docker version 24.0.7, build afdd53b + ``` + If your version is below 20.10.0, please update Docker following the [official upgrade guide](https://docs.docker.com/engine/install/). + +2. **Check Docker Compose version:** + ```sh + # For Docker Compose V2 + docker compose version + # Should output something like: Docker Compose version v2.21.0 + ``` + + If you get a "command not found" error, you might have the older version. Check with: + ```sh + docker-compose version + ``` + +### Installing/Updating Docker + +1. **For Ubuntu/Debian:** + ```sh + # Remove old versions + sudo apt-get remove docker docker-engine docker.io containerd runc + + # Install latest version + curl -fsSL https://get.docker.com -o get-docker.sh + sudo sh get-docker.sh + ``` + +2. **For Windows/Mac:** + - Download and install [Docker Desktop](https://www.docker.com/products/docker-desktop/) + +3. **For other systems:** + - Follow the [official Docker installation guide](https://docs.docker.com/engine/install/) + ## Starting the Development Environment 1. **Clone the Repository** @@ -38,13 +77,13 @@ This guide explains how to start the development environment for the project usi To build and run the entire development environment, use the following command: ```sh - docker-compose -f docker-compose.dev.yaml up --build + docker compose -f devops/docker-compose.dev.yaml up --build ``` For Windows users, use this command to build and start the development environment: ```sh - docker-compose -f docker-compose.dev-windows.yaml up --build + docker compose -f devops/docker-compose.dev-windows.yaml up --build ``` This command will: @@ -110,13 +149,13 @@ To simplify repetitive tasks and ensure consistency, a `Makefile` is included in - **Docker Build Issues**: If changes in dependencies are not reflected, you may need to clear Docker's cache: ```sh - docker-compose -f docker-compose.dev.yaml build --no-cache + docker compose -f devops/docker-compose.dev.yaml build --no-cache ``` Windows users: ```sh - docker-compose -f docker-compose.dev-windows.yaml build --no-cache + docker compose -f devops/docker-compose.dev-windows.yaml build --no-cache ``` ## How to run test cases @@ -144,13 +183,13 @@ poetry run pytest To stop the environment and remove containers, use: ```sh -docker-compose -f docker-compose.dev.yaml down +docker compose -f devops/docker-compose.dev.yaml down ``` windows users: ```sh -docker-compose -f docker-compose.dev-windows.yaml down +docker compose -f devops/docker-compose.dev-windows.yaml down ``` This command stops all running containers and removes them, but the data volumes will persist. @@ -160,13 +199,13 @@ This command stops all running containers and removes them, but the data volumes If you have made changes to the code or Docker configuration, rebuild the containers: ```sh -docker-compose -f docker-compose.dev.yaml up --build +docker compose -f devops/docker-compose.dev.yaml up --build ``` windows users: ```sh -docker-compose -f docker-compose.dev-windows.yaml up --build +docker compose -f devops/docker-compose.dev-windows.yaml up --build ``` ## About Celery @@ -184,7 +223,7 @@ This project utilizes Celery to handle asynchronous tasks. The Celery workers an To start the Celery worker and Celery Beat services, use the following command in the terminal within your project directory: ```bash -docker-compose up -d celery celery_beat +docker compose up -d celery celery_beat ``` ### Stopping Celery @@ -200,7 +239,7 @@ docker-compose stop celery celery_beat If you want to purge all tasks from the Celery queue, you can do this by executing ```bash -docker-compose run --rm celery celery -A spotnet_tracker.celery_config purge +docker compose run --rm celery celery -A spotnet_tracker.celery_config purge ``` ## How to add test data @@ -208,13 +247,13 @@ docker-compose run --rm celery celery -A spotnet_tracker.celery_config purge 1. Run dev container ``` -docker-compose -f docker-compose.dev.yaml up --build +docker compose -f devops/docker-compose.dev.yaml up --build ``` windows only: ``` -docker-compose -f docker-compose.dev-windows.yaml up --build +docker compose -f devops/docker-compose.dev-windows.yaml up --build ``` 2. In new terminal window run command to populate db @@ -228,13 +267,13 @@ docker exec -ti backend_dev python -m web_app.db.seed_data Run up docker containers ```bash -docker-compose -f docker-compose.dev.yaml up --build +docker compose -f docker-compose.dev.yaml up --build ``` Windows users: ```bash -docker-compose -f docker-compose.dev-windows.yaml up --build +docker compose -f devops/docker-compose.dev-windows.yaml up --build ``` Go to backend container in new terminal window diff --git a/docker-compose.back.yaml b/devops/docker-compose.back.yaml similarity index 86% rename from docker-compose.back.yaml rename to devops/docker-compose.back.yaml index cd4b08be..fdff0d14 100644 --- a/docker-compose.back.yaml +++ b/devops/docker-compose.back.yaml @@ -6,14 +6,14 @@ networks: services: backend: - build: . + build: .. command: bash /app/entrypoint.sh container_name: backend_dev volumes: - - ./entrypoint.sh:/app/entrypoint.sh - - .:/app + - ../entrypoint.sh:/app/entrypoint.sh + - ..:/app env_file: - - .env.dev + - ../.env.dev expose: - "8000" ports: @@ -38,7 +38,7 @@ services: POSTGRES_PASSWORD: password volumes: - postgres_data_dev:/var/lib/postgresql/data - - ./init-db:/docker-entrypoint-initdb.d + - ../init-db:/docker-entrypoint-initdb.d networks: - app_network ports: diff --git a/docker-compose.dev-windows.yaml b/devops/docker-compose.dev-windows.yaml similarity index 88% rename from docker-compose.dev-windows.yaml rename to devops/docker-compose.dev-windows.yaml index acd46b58..c79453b8 100644 --- a/docker-compose.dev-windows.yaml +++ b/devops/docker-compose.dev-windows.yaml @@ -8,14 +8,14 @@ networks: services: backend: build: - context: . + context: .. dockerfile: Dockerfile.windows command: ["/bin/bash", "-c", "chmod +x /app/entrypoint.sh && /app/entrypoint.sh"] container_name: backend_dev volumes: - - .:/app + - ..:/app env_file: - - .env.dev + - ../.env.dev ports: - "8000:8000" networks: @@ -38,7 +38,7 @@ services: POSTGRES_PASSWORD: password volumes: - postgres_data_dev:/var/lib/postgresql/data - - ./init-db:/docker-entrypoint-initdb.d + - ../init-db:/docker-entrypoint-initdb.d networks: - app_network ports: @@ -51,11 +51,11 @@ services: frontend: build: - context: ./frontend + context: ../frontend dockerfile: Dockerfile.dev container_name: frontend_dev volumes: - - ./frontend:/app + - ../frontend:/app ports: - "3000:80" networks: diff --git a/docker-compose.dev.yaml b/devops/docker-compose.dev.yaml similarity index 83% rename from docker-compose.dev.yaml rename to devops/docker-compose.dev.yaml index bb9d8db5..eac2e03b 100644 --- a/docker-compose.dev.yaml +++ b/devops/docker-compose.dev.yaml @@ -1,4 +1,4 @@ -version: '3.8' +version: "3.8" networks: app_network: @@ -6,14 +6,14 @@ networks: services: backend: - build: . + build: .. command: ["/app/entrypoint.sh"] container_name: backend_dev volumes: - - ./entrypoint.sh:/app/entrypoint.sh - - .:/app + - ../entrypoint.sh:/app/entrypoint.sh + - ..:/app env_file: - - .env.dev + - ../.env.dev ports: - "8000:8000" networks: @@ -36,7 +36,7 @@ services: POSTGRES_PASSWORD: password volumes: - postgres_data_dev:/var/lib/postgresql/data - - ./init-db:/docker-entrypoint-initdb.d + - ../init-db:/docker-entrypoint-initdb.d networks: - app_network ports: @@ -49,11 +49,11 @@ services: frontend: build: - context: ./frontend + context: ../frontend dockerfile: Dockerfile.dev container_name: frontend_dev volumes: - - ./frontend:/app + - ../frontend:/app ports: - "3000:80" networks: diff --git a/docker-compose.yaml b/devops/docker-compose.yaml similarity index 86% rename from docker-compose.yaml rename to devops/docker-compose.yaml index 33bc9e77..ba8b6c19 100644 --- a/docker-compose.yaml +++ b/devops/docker-compose.yaml @@ -6,27 +6,27 @@ networks: services: nginx: - build: ./frontend + build: ../frontend container_name: nginx restart: unless-stopped ports: - "80:80" - "443:443" volumes: - - ./certs/spotnet.xyz.chain.crt:/etc/nginx/spotnet.xyz.chain.crt:ro - - ./certs/spotnet.xyz.key:/etc/nginx/spotnet.xyz.key:ro + - ../certs/spotnet.xyz.chain.crt:/etc/nginx/spotnet.xyz.chain.crt:ro + - ../certs/spotnet.xyz.key:/etc/nginx/spotnet.xyz.key:ro depends_on: - backend networks: - app_network backend: - build: . + build: .. command: ["bash", "/app/entrypoint.sh"] restart: always volumes: - - ./entrypoint.sh:/app/entrypoint.sh + - ../entrypoint.sh:/app/entrypoint.sh env_file: - - .env + - ../.env depends_on: - db expose: From 3c80dd6653ed22d77c1a92e2563825714ea55b04 Mon Sep 17 00:00:00 2001 From: bernard karaba Date: Wed, 22 Jan 2025 23:25:09 +0300 Subject: [PATCH 3/8] fix test cases --- web_app/api/dashboard.py | 7 ++++++- web_app/api/serializers/dashboard.py | 4 ++-- web_app/tests/test_dashboard.py | 1 + 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/web_app/api/dashboard.py b/web_app/api/dashboard.py index 86b82a27..aa1c24a0 100644 --- a/web_app/api/dashboard.py +++ b/web_app/api/dashboard.py @@ -51,7 +51,7 @@ async def get_dashboard(wallet_id: str) -> DashboardResponse: borrowed="0", balance="0", position_id="0", - deposit_data={}, + deposit_data={"token": "", "amount": "0"}, ) if not contract_address: return default_dashboard_response @@ -91,6 +91,11 @@ async def get_dashboard(wallet_id: str) -> DashboardResponse: position_balance, position_multiplier ) token_symbol = first_opened_position["token_symbol"] + extra_deposits = position_db_connector.get_extra_deposits_data( + first_opened_position["id"] + ) + + # symbol = extra_deposits.get("token_symbol") deposit_data = { "token": token_symbol, "amount": str(Decimal(position_balance) + Decimal(extra_deposit_balance)), diff --git a/web_app/api/serializers/dashboard.py b/web_app/api/serializers/dashboard.py index 25e89417..9aa5bf80 100644 --- a/web_app/api/serializers/dashboard.py +++ b/web_app/api/serializers/dashboard.py @@ -4,7 +4,7 @@ from datetime import datetime from decimal import Decimal -from typing import Dict, Union +from typing import Dict from pydantic import BaseModel, Field @@ -50,7 +50,7 @@ class DashboardResponse(BaseModel): example="12", description="The position ID.", ) - deposit_data: Dict[str, Union[str, float]] = Field( + deposit_data: Dict[str, str] = Field( ..., example={"token": "ETH", "amount": "100.0"}, description="The deposit data including token and amount.", diff --git a/web_app/tests/test_dashboard.py b/web_app/tests/test_dashboard.py index a8ff6dfb..b608abb7 100644 --- a/web_app/tests/test_dashboard.py +++ b/web_app/tests/test_dashboard.py @@ -305,6 +305,7 @@ async def test_empty_positions( "balance": "0", "health_ratio": "0", "position_id": "0", + "deposit_data": {"token": "", "amount": "0"}, } From 657daa6d1b2ba8ee76d8132b0b7f73e919123344 Mon Sep 17 00:00:00 2001 From: Anton Rudenko Date: Thu, 23 Jan 2025 00:30:44 +0400 Subject: [PATCH 4/8] feat: add bug report API with validation and Sentry integration --- web_app/api/serializers/user.py | 27 +++++++++- web_app/api/user.py | 68 +++++++++++++++++++++++-- web_app/tests/test_user.py | 88 +++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+), 4 deletions(-) diff --git a/web_app/api/serializers/user.py b/web_app/api/serializers/user.py index a1464d91..6aab4360 100644 --- a/web_app/api/serializers/user.py +++ b/web_app/api/serializers/user.py @@ -2,8 +2,9 @@ This module defines the serializers for the user data. """ -from decimal import Decimal from datetime import datetime +from decimal import Decimal + from pydantic import BaseModel, Field @@ -100,3 +101,27 @@ class SubscribeToNotificationRequest(BaseModel): None, example="123456789", description="Telegram ID of the user" ) wallet_id: str = Field(..., example="0xabc123", description="Wallet ID of the user") + + +class BugReportRequest(BaseModel): + """ + Pydantic model for bug report request. + """ + + wallet_id: str = Field( + ..., pattern=r"^0x[a-fA-F0-9]+$", description="User's wallet ID" + ) + telegram_id: str | None = Field( + None, pattern=r"^\d+$", description="User's Telegram ID" + ) + bug_description: str = Field( + ..., min_length=1, description="Description of the bug" + ) + + +class BugReportResponse(BaseModel): + """ + Pydantic model for bug report response. + """ + + message: str = Field(..., example="Bug report submitted successfully") diff --git a/web_app/api/user.py b/web_app/api/user.py index de6b4d06..40bf5eb8 100644 --- a/web_app/api/user.py +++ b/web_app/api/user.py @@ -1,4 +1,3 @@ - """ This module handles user-related API endpoints. """ @@ -6,22 +5,26 @@ import logging from decimal import Decimal +import sentry_sdk from fastapi import APIRouter, HTTPException + from web_app.api.serializers.transaction import UpdateUserContractRequest from web_app.api.serializers.user import ( + BugReportRequest, + BugReportResponse, CheckUserResponse, GetStatsResponse, GetUserContractAddressResponse, SubscribeToNotificationRequest, UpdateUserContractResponse, ) -from web_app.contract_tools.mixins import PositionMixin, DashboardMixin +from web_app.contract_tools.blockchain_call import CLIENT +from web_app.contract_tools.mixins import DashboardMixin, PositionMixin from web_app.db.crud import ( PositionDBConnector, TelegramUserDBConnector, UserDBConnector, ) -from web_app.contract_tools.blockchain_call import CLIENT logger = logging.getLogger(__name__) router = APIRouter() # Initialize the router @@ -257,3 +260,62 @@ async def get_stats() -> GetStatsResponse: except Exception as e: logger.error(f"Error in get_stats: {e}") raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}") + + +@router.post( + "/api/save-bug-report", + response_model=BugReportResponse, + tags=["Bug Reports"], + summary="Submit a bug report", + response_description="Returns confirmation of bug report submission", +) +async def save_bug_report(report: BugReportRequest) -> BugReportResponse: + """ + Save a bug report and send it to Sentry for tracking. + + ### Parameters: + - **report**: An instance of BugReportRequest containing: + - **wallet_id**: User's wallet ID (0x format) + - **telegram_id**: Optional Telegram ID (numeric) + - **bug_description**: Detailed bug description (1-1000 chars) + + ### Returns: + - BugReportResponse with confirmation message + + ### Raises: + - 422: Validation error + - 500: Internal server error + """ + try: + if report.wallet_id.strip() == "" or report.bug_description.strip() == "": + raise HTTPException( + status_code=422, detail="Wallet ID and bug description cannot be empty" + ) + + sentry_sdk.set_user( + {"wallet_id": report.wallet_id, "telegram_id": report.telegram_id} + ) + + sentry_sdk.set_context( + "bug_report", + { + "wallet_id": report.wallet_id, + "telegram_id": report.telegram_id, + "description": report.bug_description, + }, + ) + + sentry_sdk.capture_message( + f"Bug Report from {report.wallet_id}", + level="error", + extras={"description": report.bug_description}, + ) + + return BugReportResponse(message="Bug report submitted successfully") + except Exception as e: + if isinstance(e, HTTPException): + raise + + logger.error(f"Failed to submit bug report: {str(e)}") + sentry_sdk.capture_exception(e) + raise HTTPException(status_code=500, detail="Failed to submit bug report") diff --git a/web_app/tests/test_user.py b/web_app/tests/test_user.py index 0bc20ff9..59d1405f 100644 --- a/web_app/tests/test_user.py +++ b/web_app/tests/test_user.py @@ -349,3 +349,91 @@ async def test_subscribe_to_notification( # mock_get_contract_address.assert_called_once_with(wallet_id) # if contract_address: # mock_withdraw_all.assert_called_once_with(contract_address) + + +@pytest.mark.asyncio +@patch("sentry_sdk.capture_message") +@patch("sentry_sdk.set_user") +@patch("sentry_sdk.set_context") +@pytest.mark.parametrize( + "report_data, expected_status, expected_response", + [ + ( + { + "wallet_id": "0x123", + "telegram_id": "456", + "bug_description": "Test bug description", + }, + 200, + {"message": "Bug report submitted successfully"}, + ), + ( + {"wallet_id": "0x123", "bug_description": "Test without telegram"}, + 200, + {"message": "Bug report submitted successfully"}, + ), + ], +) +async def test_save_bug_report_success( + mock_set_context, + mock_set_user, + mock_capture_message, + client, + report_data, + expected_status, + expected_response, +): + """Test successful bug report submission""" + response = client.post("/api/save-bug-report", json=report_data) + + assert response.status_code == expected_status + assert response.json() == expected_response + mock_set_user.assert_called_once() + mock_set_context.assert_called_once() + mock_capture_message.assert_called_once() + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "report_data, expected_status, error_message", + [ + ( + {"wallet_id": "invalid", "bug_description": "Test"}, + 422, + "String should match pattern '^0x[a-fA-F0-9]+$'", + ), + ( + {"wallet_id": "0x123", "telegram_id": "abc", "bug_description": "Test"}, + 422, + "String should match pattern '^\\d+$'", + ), + ( + {"wallet_id": "0x123", "bug_description": ""}, + 422, + "String should have at least 1 character", + ), + ( + {"telegram_id": "456", "bug_description": "Missing wallet"}, + 422, + "Field required", + ), + ( + {"wallet_id": "0x123", "telegram_id": "456"}, + 422, + "Field required", + ), + ( + {}, + 422, + "Field required", + ), + ], +) +async def test_save_bug_report_validation( + client, report_data, expected_status, error_message +): + """Test bug report validation failures""" + response = client.post("/api/save-bug-report", json=report_data) + + assert response.status_code == expected_status + assert response.json()["detail"][0]["msg"] == error_message From de69bb431805f38bb8e1a878fbfebba2e8afeb28 Mon Sep 17 00:00:00 2001 From: bernard karaba Date: Thu, 23 Jan 2025 01:10:22 +0300 Subject: [PATCH 5/8] update deposit_data to list of dicts --- web_app/api/dashboard.py | 13 ++++++------- web_app/api/serializers/dashboard.py | 4 ++-- web_app/tests/test_dashboard.py | 12 +++++++----- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/web_app/api/dashboard.py b/web_app/api/dashboard.py index aa1c24a0..58f42cca 100644 --- a/web_app/api/dashboard.py +++ b/web_app/api/dashboard.py @@ -51,7 +51,7 @@ async def get_dashboard(wallet_id: str) -> DashboardResponse: borrowed="0", balance="0", position_id="0", - deposit_data={"token": "", "amount": "0"}, + deposit_data=[], ) if not contract_address: return default_dashboard_response @@ -91,15 +91,14 @@ async def get_dashboard(wallet_id: str) -> DashboardResponse: position_balance, position_multiplier ) token_symbol = first_opened_position["token_symbol"] - extra_deposits = position_db_connector.get_extra_deposits_data( + extra_deposits = position_db_connector.get_extra_deposits_by_position_id( first_opened_position["id"] ) + deposit_data = [ + {"token": deposit.token_symbol, "amount": deposit.amount} + for deposit in extra_deposits + ] - # symbol = extra_deposits.get("token_symbol") - deposit_data = { - "token": token_symbol, - "amount": str(Decimal(position_balance) + Decimal(extra_deposit_balance)), - } return DashboardResponse( health_ratio=health_ratio, multipliers={token_symbol: str(position_multiplier)}, diff --git a/web_app/api/serializers/dashboard.py b/web_app/api/serializers/dashboard.py index 9aa5bf80..6b17209c 100644 --- a/web_app/api/serializers/dashboard.py +++ b/web_app/api/serializers/dashboard.py @@ -4,7 +4,7 @@ from datetime import datetime from decimal import Decimal -from typing import Dict +from typing import Dict, List from pydantic import BaseModel, Field @@ -50,7 +50,7 @@ class DashboardResponse(BaseModel): example="12", description="The position ID.", ) - deposit_data: Dict[str, str] = Field( + deposit_data: List[Dict[str, str]] = Field( ..., example={"token": "ETH", "amount": "100.0"}, description="The deposit data including token and amount.", diff --git a/web_app/tests/test_dashboard.py b/web_app/tests/test_dashboard.py index b608abb7..db110bc6 100644 --- a/web_app/tests/test_dashboard.py +++ b/web_app/tests/test_dashboard.py @@ -147,10 +147,12 @@ async def test_get_dashboard_success(): "balance": str(balance), "health_ratio": "1.2", "position_id": str(id), - "deposit_data": { - "token": "ETH", - "amount": str(mock_position_balance + mock_extra_deposit), - }, + "deposit_data": [ + { + "token": "ETH", + "amount": str(mock_extra_deposit), + } + ], } @@ -305,7 +307,7 @@ async def test_empty_positions( "balance": "0", "health_ratio": "0", "position_id": "0", - "deposit_data": {"token": "", "amount": "0"}, + "deposit_data": [], } From fb9fba41b99031d38d32e67cf88a528f602ce5d5 Mon Sep 17 00:00:00 2001 From: bernard karaba Date: Thu, 23 Jan 2025 10:08:39 +0300 Subject: [PATCH 6/8] Update example in deposit_data field of DashboardResponse model. --- web_app/api/serializers/dashboard.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web_app/api/serializers/dashboard.py b/web_app/api/serializers/dashboard.py index 6b17209c..c8944ca2 100644 --- a/web_app/api/serializers/dashboard.py +++ b/web_app/api/serializers/dashboard.py @@ -52,6 +52,9 @@ class DashboardResponse(BaseModel): ) deposit_data: List[Dict[str, str]] = Field( ..., - example={"token": "ETH", "amount": "100.0"}, + example=[ + {"token": "ETH", "amount": "100.0"}, + {"token": "STK", "amount": "50.0"}, + ], description="The deposit data including token and amount.", ) From 43727a5d7fd7c32d6b485d159b1c451455895819 Mon Sep 17 00:00:00 2001 From: bernard karaba Date: Thu, 23 Jan 2025 11:02:46 +0300 Subject: [PATCH 7/8] fix test cases --- .../a50b7c912e8c_migration_message.py | 28 +++++++++++++++++++ web_app/tests/test_dashboard.py | 7 +---- 2 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 web_app/alembic/versions/a50b7c912e8c_migration_message.py diff --git a/web_app/alembic/versions/a50b7c912e8c_migration_message.py b/web_app/alembic/versions/a50b7c912e8c_migration_message.py new file mode 100644 index 00000000..7af341fe --- /dev/null +++ b/web_app/alembic/versions/a50b7c912e8c_migration_message.py @@ -0,0 +1,28 @@ +"""migration message + +Revision ID: a50b7c912e8c +Revises: 9662d09015ae +Create Date: 2025-01-23 07:41:32.699318 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'a50b7c912e8c' +down_revision = '9662d09015ae' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git a/web_app/tests/test_dashboard.py b/web_app/tests/test_dashboard.py index db110bc6..8fd7659d 100644 --- a/web_app/tests/test_dashboard.py +++ b/web_app/tests/test_dashboard.py @@ -147,12 +147,7 @@ async def test_get_dashboard_success(): "balance": str(balance), "health_ratio": "1.2", "position_id": str(id), - "deposit_data": [ - { - "token": "ETH", - "amount": str(mock_extra_deposit), - } - ], + "deposit_data": [], } From a2bb79103dcf57291a02ca4e672662e16d011331 Mon Sep 17 00:00:00 2001 From: bernard karaba Date: Thu, 23 Jan 2025 11:25:46 +0300 Subject: [PATCH 8/8] fix failing test --- .../a50b7c912e8c_migration_message.py | 28 ------------------- 1 file changed, 28 deletions(-) delete mode 100644 web_app/alembic/versions/a50b7c912e8c_migration_message.py diff --git a/web_app/alembic/versions/a50b7c912e8c_migration_message.py b/web_app/alembic/versions/a50b7c912e8c_migration_message.py deleted file mode 100644 index 7af341fe..00000000 --- a/web_app/alembic/versions/a50b7c912e8c_migration_message.py +++ /dev/null @@ -1,28 +0,0 @@ -"""migration message - -Revision ID: a50b7c912e8c -Revises: 9662d09015ae -Create Date: 2025-01-23 07:41:32.699318 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'a50b7c912e8c' -down_revision = '9662d09015ae' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ###