diff --git a/backend/moonstream/middleware.py b/backend/moonstream/middleware.py index a872095a..7bb94a84 100644 --- a/backend/moonstream/middleware.py +++ b/backend/moonstream/middleware.py @@ -1,10 +1,11 @@ import logging -from typing import Awaitable, Callable, Dict, Optional +from typing import Any, Awaitable, Callable, Dict, Optional from bugout.data import BugoutUser from bugout.exceptions import BugoutResponseException +from fastapi import HTTPException, Request, Response +from starlette.background import BackgroundTask from starlette.middleware.base import BaseHTTPMiddleware -from fastapi import Request, Response from .reporter import reporter from .settings import MOONSTREAM_APPLICATION_ID, bugout_client as bc @@ -12,6 +13,44 @@ from .settings import MOONSTREAM_APPLICATION_ID, bugout_client as bc logger = logging.getLogger(__name__) +class MoonstreamResponse(Response): + """ + Extended Response to handle 500 Internal server errors + and send crash reports. + """ + + def __init__( + self, + content: Any = None, + status_code: int = 200, + headers: dict = None, + media_type: str = None, + background: BackgroundTask = None, + internal_error: Exception = None, + ): + super().__init__(content, status_code, headers, media_type, background) + if internal_error is not None: + reporter.error_report(internal_error) + + +class MoonstreamHTTPException(HTTPException): + """ + Extended HTTPException to handle 500 Internal server errors + and send crash reports. + """ + + def __init__( + self, + status_code: int, + detail: Any = None, + headers: Optional[Dict[str, Any]] = None, + internal_error: Exception = None, + ): + super().__init__(status_code, detail, headers) + if internal_error is not None: + reporter.error_report(internal_error) + + class BroodAuthMiddleware(BaseHTTPMiddleware): """ Checks the authorization header on the request. If it represents a verified Brood user, @@ -62,8 +101,9 @@ class BroodAuthMiddleware(BaseHTTPMiddleware): return Response(status_code=e.status_code, content=e.detail) except Exception as e: logger.error(f"Error processing Brood response: {str(e)}") - reporter.error_report(e) - return Response(status_code=500, content="Internal server error") + return MoonstreamResponse( + status_code=500, content="Internal server error", internal_error=e + ) request.state.user = user request.state.token = user_token diff --git a/backend/moonstream/routes/address_info.py b/backend/moonstream/routes/address_info.py index b9794937..c985c683 100644 --- a/backend/moonstream/routes/address_info.py +++ b/backend/moonstream/routes/address_info.py @@ -3,15 +3,14 @@ from typing import Dict, List, Optional from sqlalchemy.sql.expression import true -from fastapi import FastAPI, Depends, Query, HTTPException +from fastapi import FastAPI, Depends, Query from fastapi.middleware.cors import CORSMiddleware from moonstreamdb.db import yield_db_session from sqlalchemy.orm import Session from .. import actions from .. import data -from ..middleware import BroodAuthMiddleware -from ..reporter import reporter +from ..middleware import BroodAuthMiddleware, MoonstreamHTTPException from ..settings import DOCS_TARGET_PATH, ORIGINS, DOCS_PATHS from ..version import MOONSTREAM_VERSION @@ -74,16 +73,15 @@ async def addresses_labels_bulk_handler( about known addresses. """ if limit > 100: - raise HTTPException( + raise MoonstreamHTTPException( status_code=406, detail="The limit cannot exceed 100 addresses" ) try: addresses_response = actions.get_address_labels( db_session=db_session, start=start, limit=limit, addresses=addresses ) - except Exception as err: - logger.error(f"Unable to get info about Ethereum addresses {err}") - reporter.error_report(err) - raise HTTPException(status_code=500) + except Exception as e: + logger.error(f"Unable to get info about Ethereum addresses {e}") + raise MoonstreamHTTPException(status_code=500, internal_error=e) return addresses_response diff --git a/backend/moonstream/routes/streams.py b/backend/moonstream/routes/streams.py index 35bfe0a2..10d0426c 100644 --- a/backend/moonstream/routes/streams.py +++ b/backend/moonstream/routes/streams.py @@ -5,14 +5,14 @@ import logging from typing import Dict, List, Optional from bugout.data import BugoutResource -from fastapi import FastAPI, HTTPException, Request, Query, Depends +from fastapi import FastAPI, Request, Query, Depends from fastapi.middleware.cors import CORSMiddleware from moonstreamdb import db from sqlalchemy.orm import Session from .. import data -from ..middleware import BroodAuthMiddleware +from ..middleware import BroodAuthMiddleware, MoonstreamHTTPException from ..providers import ( ReceivingEventsException, event_providers, @@ -21,7 +21,6 @@ from ..providers import ( next_event, previous_event, ) -from ..reporter import reporter from ..settings import ( DOCS_TARGET_PATH, MOONSTREAM_ADMIN_ACCESS_TOKEN, @@ -137,12 +136,10 @@ async def stream_handler( ) except ReceivingEventsException as e: logger.error("Error receiving events from provider") - reporter.error_report(e) - raise HTTPException(status_code=500) + raise MoonstreamHTTPException(status_code=500, internal_error=e) except Exception as e: logger.error("Unable to get events") - reporter.error_report(e) - raise HTTPException(status_code=500) + raise MoonstreamHTTPException(status_code=500, internal_error=e) response = data.GetEventsResponse(stream_boundary=stream_boundary, events=events) return response @@ -182,12 +179,10 @@ async def latest_events_handler( ) except ReceivingEventsException as e: logger.error("Error receiving events from provider") - reporter.error_report(e) - raise HTTPException(status_code=500) + raise MoonstreamHTTPException(status_code=500, internal_error=e) except Exception as e: logger.error("Unable to get latest events") - reporter.error_report(e) - raise HTTPException(status_code=500) + raise MoonstreamHTTPException(status_code=500, internal_error=e) return events @@ -239,12 +234,10 @@ async def next_event_handler( ) except ReceivingEventsException as e: logger.error("Error receiving events from provider") - reporter.error_report(e) - raise HTTPException(status_code=500) + raise MoonstreamHTTPException(status_code=500, internal_error=e) except Exception as e: logger.error("Unable to get next events") - reporter.error_report(e) - raise HTTPException(status_code=500) + raise MoonstreamHTTPException(status_code=500, internal_error=e) return event @@ -296,11 +289,9 @@ async def previous_event_handler( ) except ReceivingEventsException as e: logger.error("Error receiving events from provider") - reporter.error_report(e) - raise HTTPException(status_code=500) + raise MoonstreamHTTPException(status_code=500, internal_error=e) except Exception as e: logger.error("Unable to get previous events") - reporter.error_report(e) - raise HTTPException(status_code=500) + raise MoonstreamHTTPException(status_code=500, internal_error=e) return event diff --git a/backend/moonstream/routes/subscriptions.py b/backend/moonstream/routes/subscriptions.py index f05eea0e..f86f4686 100644 --- a/backend/moonstream/routes/subscriptions.py +++ b/backend/moonstream/routes/subscriptions.py @@ -6,12 +6,12 @@ from typing import Dict, List, Optional from bugout.data import BugoutResource, BugoutResources from bugout.exceptions import BugoutResponseException -from fastapi import FastAPI, HTTPException, Request, Form +from fastapi import FastAPI, Request, Form from fastapi.middleware.cors import CORSMiddleware from ..admin import subscription_types from .. import data -from ..middleware import BroodAuthMiddleware +from ..middleware import BroodAuthMiddleware, MoonstreamHTTPException from ..reporter import reporter from ..settings import ( DOCS_TARGET_PATH, @@ -78,7 +78,7 @@ async def add_subscription_handler( ] if subscription_type_id not in available_subscription_type_ids: - raise HTTPException( + raise MoonstreamHTTPException( status_code=404, detail=f"Invalid subscription type: {subscription_type_id}.", ) @@ -100,10 +100,11 @@ async def add_subscription_handler( application_id=MOONSTREAM_APPLICATION_ID, resource_data=resource_data, ) + except BugoutResponseException as e: + raise MoonstreamHTTPException(status_code=e.status_code, detail=e.detail) except Exception as e: logger.error(f"Error creating subscription resource: {str(e)}") - reporter.error_report(e) - raise HTTPException(status_code=500) + raise MoonstreamHTTPException(status_code=500, internal_error=e) return data.SubscriptionResourceData( id=str(resource.id), @@ -128,11 +129,10 @@ async def delete_subscription_handler(request: Request, subscription_id: str): try: deleted_resource = bc.delete_resource(token=token, resource_id=subscription_id) except BugoutResponseException as e: - raise HTTPException(status_code=e.status_code, detail=e.detail) + raise MoonstreamHTTPException(status_code=e.status_code, detail=e.detail) except Exception as e: logger.error(f"Error deleting subscription: {str(e)}") - reporter.error_report(e) - raise HTTPException(status_code=500) + raise MoonstreamHTTPException(status_code=500, internal_error=e) return data.SubscriptionResourceData( id=str(deleted_resource.id), @@ -156,12 +156,14 @@ async def get_subscriptions_handler(request: Request) -> data.SubscriptionsListR } try: resources: BugoutResources = bc.list_resources(token=token, params=params) + except BugoutResponseException as e: + raise MoonstreamHTTPException(status_code=e.status_code, detail=e.detail) except Exception as e: logger.error( f"Error listing subscriptions for user ({request.user.id}) with token ({request.state.token}), error: {str(e)}" ) reporter.error_report(e) - raise HTTPException(status_code=500) + raise MoonstreamHTTPException(status_code=500, internal_error=e) return data.SubscriptionsListResponse( subscriptions=[ @@ -211,11 +213,10 @@ async def update_subscriptions_handler( ).dict(), ) except BugoutResponseException as e: - raise HTTPException(status_code=e.status_code, detail=e.detail) + raise MoonstreamHTTPException(status_code=e.status_code, detail=e.detail) except Exception as e: logger.error(f"Error getting user subscriptions: {str(e)}") - reporter.error_report(e) - raise HTTPException(status_code=500) + raise MoonstreamHTTPException(status_code=500, internal_error=e) return data.SubscriptionResourceData( id=str(resource.id), @@ -241,9 +242,10 @@ async def list_subscription_types() -> data.SubscriptionTypesListResponse: data.SubscriptionTypeResourceData.validate(resource.resource_data) for resource in response.resources ] + except BugoutResponseException as e: + raise MoonstreamHTTPException(status_code=e.status_code, detail=e.detail) except Exception as e: logger.error(f"Error reading subscription types from Brood API: {str(e)}") - reporter.error_report(e) - raise HTTPException(status_code=500) + raise MoonstreamHTTPException(status_code=500, internal_error=e) return data.SubscriptionTypesListResponse(subscription_types=results) diff --git a/backend/moonstream/routes/users.py b/backend/moonstream/routes/users.py index 60090740..4407a67e 100644 --- a/backend/moonstream/routes/users.py +++ b/backend/moonstream/routes/users.py @@ -7,16 +7,10 @@ import uuid from bugout.data import BugoutToken, BugoutUser from bugout.exceptions import BugoutResponseException -from fastapi import ( - FastAPI, - Form, - HTTPException, - Request, -) +from fastapi import FastAPI, Form, Request from fastapi.middleware.cors import CORSMiddleware -from ..middleware import BroodAuthMiddleware -from ..reporter import reporter +from ..middleware import BroodAuthMiddleware, MoonstreamHTTPException from ..settings import ( MOONSTREAM_APPLICATION_ID, DOCS_TARGET_PATH, @@ -76,10 +70,9 @@ async def create_user_handler( application_id=MOONSTREAM_APPLICATION_ID, ) except BugoutResponseException as e: - raise HTTPException(status_code=e.status_code, detail=e.detail) + raise MoonstreamHTTPException(status_code=e.status_code, detail=e.detail) except Exception as e: - reporter.error_report(e) - raise HTTPException(status_code=500) + raise MoonstreamHTTPException(status_code=500, internal_error=e) return user @@ -94,10 +87,9 @@ async def restore_password_handler(email: str = Form(...)) -> Dict[str, Any]: try: response = bc.restore_password(email=email) except BugoutResponseException as e: - raise HTTPException(status_code=e.status_code, detail=e.detail) + raise MoonstreamHTTPException(status_code=e.status_code, detail=e.detail) except Exception as e: - reporter.error_report(e) - raise HTTPException(status_code=500) + raise MoonstreamHTTPException(status_code=500, internal_error=e) return response @@ -108,10 +100,9 @@ async def reset_password_handler( try: response = bc.reset_password(reset_id=reset_id, new_password=new_password) except BugoutResponseException as e: - raise HTTPException(status_code=e.status_code, detail=e.detail) + raise MoonstreamHTTPException(status_code=e.status_code, detail=e.detail) except Exception as e: - reporter.error_report(e) - raise HTTPException(status_code=500) + raise MoonstreamHTTPException(status_code=500, internal_error=e) return response @@ -125,10 +116,9 @@ async def change_password_handler( token=token, current_password=current_password, new_password=new_password ) except BugoutResponseException as e: - raise HTTPException(status_code=e.status_code, detail=e.detail) + raise MoonstreamHTTPException(status_code=e.status_code, detail=e.detail) except Exception as e: - reporter.error_report(e) - raise HTTPException(status_code=500) + raise MoonstreamHTTPException(status_code=500, internal_error=e) return user @@ -141,10 +131,9 @@ async def delete_user_handler( try: user = bc.delete_user(token=token, user_id=user.id, password=password) except BugoutResponseException as e: - raise HTTPException(status_code=e.status_code, detail=e.detail) + raise MoonstreamHTTPException(status_code=e.status_code, detail=e.detail) except Exception as e: - reporter.error_report(e) - raise HTTPException(status_code=500) + raise MoonstreamHTTPException(status_code=500, internal_error=e) return user @@ -159,12 +148,11 @@ async def login_handler( application_id=MOONSTREAM_APPLICATION_ID, ) except BugoutResponseException as e: - raise HTTPException( + raise MoonstreamHTTPException( status_code=e.status_code, detail=f"Error from Brood API: {e.detail}" ) except Exception as e: - reporter.error_report(e) - raise HTTPException(status_code=500) + raise MoonstreamHTTPException(status_code=500, internal_error=e) return token @@ -174,8 +162,7 @@ async def logout_handler(request: Request) -> uuid.UUID: try: token_id: uuid.UUID = bc.revoke_token(token=token) except BugoutResponseException as e: - raise HTTPException(status_code=e.status_code, detail=e.detail) + raise MoonstreamHTTPException(status_code=e.status_code, detail=e.detail) except Exception as e: - reporter.error_report(e) - raise HTTPException(status_code=500) + raise MoonstreamHTTPException(status_code=500, internal_error=e) return token_id diff --git a/backend/requirements.txt b/backend/requirements.txt index 6ebf10e0..4cc7c386 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -29,5 +29,6 @@ toml==0.10.2 tomli==1.0.4 types-python-dateutil==0.1.6 typing-extensions==3.10.0.0 +types-requests==2.25.6 urllib3==1.26.6 uvicorn==0.14.0 diff --git a/backend/setup.py b/backend/setup.py index 7cc23eee..06a2a807 100644 --- a/backend/setup.py +++ b/backend/setup.py @@ -18,6 +18,7 @@ setup( "python-dateutil", "uvicorn", "types-python-dateutil", + "types-requests", ], extras_require={ "dev": ["black", "mypy"],