diff --git a/api/configs/sample.env b/api/configs/sample.env index ada1367..2946acb 100644 --- a/api/configs/sample.env +++ b/api/configs/sample.env @@ -1,5 +1,6 @@ export BUGOUT_SPIRE_URL="https://spire.bugout.dev" export ENTITY_CORS_ALLOWED_ORIGINS="http://localhost:3000,https://moonstream.to,https://www.moonstream.to" +export ENTITY_HUMBUG_REPORTS_BUGOUT_TOKEN="" # Web3 signature variables export BUGOUT_APPLICATION_ID_HEADER="x-bugout-application-id" diff --git a/api/entityapi/actions.py b/api/entityapi/actions.py index 4c89d34..2cfe3e4 100644 --- a/api/entityapi/actions.py +++ b/api/entityapi/actions.py @@ -12,10 +12,17 @@ from web3 import Web3 from . import data +from .reporter import ExceptionWithReporting logger = logging.getLogger(__name__) +class UnparsableJournalEntry(ExceptionWithReporting): + """ + Unable to parse journals entry. + """ + + def to_json_types(value): """ Validate types from source to json types. @@ -30,7 +37,7 @@ def to_json_types(value): def parse_entity_to_entry( create_entity: data.Entity, -) -> Tuple[str, List[str], Dict[str, Any]]: +) -> Tuple[str, List[str], Dict[str, Any], str]: """ Parse Entity create request structure to Bugout journal scheme. """ @@ -38,12 +45,15 @@ def parse_entity_to_entry( tags: List[str] = [] content: Dict[str, Any] = {} + unknown_blockchain_address = "" + for field, vals in create_entity._iter(): if field == "address": try: address = Web3.toChecksumAddress(cast(str, vals)) except Exception: - logger.info(f"Unknown type of web3 address {vals}") + logger.info(f"Unknown type of blockchain address {vals}") + unknown_blockchain_address = vals address = vals title = f"{address} - {title}" tags.append(f"{field}:{address}") @@ -73,7 +83,7 @@ def parse_entity_to_entry( for k, v in vals.items(): content[k] = v - return title, tags, content + return title, tags, content, unknown_blockchain_address def parse_entry_to_entity( @@ -88,9 +98,11 @@ def parse_entry_to_entity( if type(entry) == BugoutJournalEntry: entity_id = entry.id else: - raise Exception("Unable to parse entity_id") + raise UnparsableJournalEntry("Unable to parse entry id") if entry.title is None: - raise Exception(f"Unable to parse entry title") + raise UnparsableJournalEntry( + f"Unable to parse entry title at entry: {str(entry.id)}" + ) name = " - ".join(entry.title.split(" - ")[1:]) address: Optional[str] = None diff --git a/api/entityapi/apps/main.py b/api/entityapi/apps/main.py index c75879a..0fdaeb1 100644 --- a/api/entityapi/apps/main.py +++ b/api/entityapi/apps/main.py @@ -18,6 +18,7 @@ from web3login.middlewares.fastapi import AuthorizationCheckMiddleware from .. import actions, data +from ..reporter import humbug_reporter from ..settings import ( BUGOUT_APPLICATION_ID_HEADER, DOCS_TARGET_PATH, @@ -151,10 +152,14 @@ async def add_entity_handler( token = request.state.token auth_type = request.state.auth_type + unknown_blockchain_address = "" try: - title, tags, content = actions.parse_entity_to_entry( - create_entity=create_request - ) + ( + title, + tags, + content, + unknown_blockchain_address, + ) = actions.parse_entity_to_entry(create_entity=create_request) response: BugoutJournalEntry = bc.create_entry( token=token, @@ -173,10 +178,23 @@ async def add_entity_handler( except BugoutResponseException as e: raise HTTPException(status_code=e.status_code, detail=e.detail) + except actions.UnparsableJournalEntry: + raise HTTPException(status_code=500, detail="Unable to form entity") except Exception as e: logger.error(e) raise HTTPException(status_code=500) + if unknown_blockchain_address != "": + humbug_reporter.custom_report( + title="Unknown type of blockchain address", + content=f"Added entity with unknown blockchain addresses " + f"`{unknown_blockchain_address}` to collection `{collection_id}` entity `{response.id}`", + tags=[ + f"collection_id:{collection_id}", + f"unknown_blockchain_address:{unknown_blockchain_address}", + ], + ) + return entity_response @@ -193,10 +211,16 @@ async def add_entity_bulk_handler( token = request.state.token auth_type = request.state.auth_type + unknown_blockchain_address = "" try: create_entries = [] for entity in create_request: - title, tags, content = actions.parse_entity_to_entry(create_entity=entity) + ( + title, + tags, + content, + unknown_blockchain_address, + ) = actions.parse_entity_to_entry(create_entity=entity) create_entries.append( { "title": title, @@ -223,10 +247,22 @@ async def add_entity_bulk_handler( except BugoutResponseException as e: raise HTTPException(status_code=e.status_code, detail=e.detail) + except actions.UnparsableJournalEntry: + raise HTTPException(status_code=500, detail="Unable to form entity") except Exception as e: logger.error(e) raise HTTPException(status_code=500) + if unknown_blockchain_address != "": + humbug_reporter.custom_report( + title="Unknown type of blockchain address - pack", + content=f"Added pack of entities with unknown blockchain addresses to collection `{collection_id}`", + tags=[ + f"collection_id:{collection_id}", + f"unknown_blockchain_address:pack", + ], + ) + return entities_response @@ -261,6 +297,8 @@ async def get_entity_handler( except BugoutResponseException as e: raise HTTPException(status_code=e.status_code, detail=e.detail) + except actions.UnparsableJournalEntry: + raise HTTPException(status_code=500, detail="Unable to form entity") except Exception as e: logger.error(e) raise HTTPException(status_code=500) @@ -283,7 +321,7 @@ async def update_entity_handler( auth_type = request.state.auth_type try: - title, tags, content = actions.parse_entity_to_entry( + title, tags, content, _ = actions.parse_entity_to_entry( create_entity=update_request ) response: BugoutJournalEntryContent = bc.update_entry_content( @@ -304,6 +342,8 @@ async def update_entity_handler( except BugoutResponseException as e: raise HTTPException(status_code=e.status_code, detail=e.detail) + except actions.UnparsableJournalEntry: + raise HTTPException(status_code=500, detail="Unable to form entity") except Exception as e: logger.error(e) raise HTTPException(status_code=500) @@ -340,6 +380,8 @@ async def get_entities_handler( except BugoutResponseException as e: raise HTTPException(status_code=e.status_code, detail=e.detail) + except actions.UnparsableJournalEntry: + raise HTTPException(status_code=500, detail="Unable to form entity") except Exception as e: logger.error(e) raise HTTPException(status_code=500) @@ -563,6 +605,8 @@ async def search_entity_handler( except BugoutResponseException as e: raise HTTPException(status_code=e.status_code, detail=e.detail) + except actions.UnparsableJournalEntry: + raise HTTPException(status_code=500, detail="Unable to form entity") except Exception as e: logger.error(e) raise HTTPException(status_code=500) diff --git a/api/entityapi/apps/public.py b/api/entityapi/apps/public.py index 0c8c3f5..59c6c1f 100644 --- a/api/entityapi/apps/public.py +++ b/api/entityapi/apps/public.py @@ -11,6 +11,7 @@ from fastapi import Body, FastAPI, HTTPException, Path, Query from .. import actions, data +from ..reporter import humbug_reporter from ..settings import DOCS_TARGET_PATH from ..settings import bugout_client as bc from ..version import VERSION @@ -176,10 +177,15 @@ async def add_public_entity_handler( """ Create public entity. """ + unknown_blockchain_address = "" + try: - title, tags, content = actions.parse_entity_to_entry( - create_entity=create_request - ) + ( + title, + tags, + content, + unknown_blockchain_address, + ) = actions.parse_entity_to_entry(create_entity=create_request) response = bc.create_public_journal_entry( journal_id=collection_id, @@ -194,10 +200,23 @@ async def add_public_entity_handler( ) except BugoutResponseException as e: raise HTTPException(status_code=e.status_code, detail=e.detail) + except actions.UnparsableJournalEntry: + raise HTTPException(status_code=500, detail="Unable to form entity") except Exception as e: logger.error(e) raise HTTPException(status_code=500) + if unknown_blockchain_address != "": + humbug_reporter.custom_report( + title="Unknown type of blockchain address", + content=f"Added public entity with unknown blockchain addresses " + f"`{unknown_blockchain_address}` to collection `{collection_id}` entity `{response.id}`", + tags=[ + f"collection_id:{collection_id}", + f"unknown_blockchain_address:{unknown_blockchain_address}", + ], + ) + return entity_response diff --git a/api/entityapi/reporter.py b/api/entityapi/reporter.py new file mode 100644 index 0000000..6226e09 --- /dev/null +++ b/api/entityapi/reporter.py @@ -0,0 +1,29 @@ +import uuid + +from humbug.consent import HumbugConsent +from humbug.report import HumbugReporter + +from .settings import ENTITY_HUMBUG_REPORTS_BUGOUT_TOKEN +from .version import VERSION + +session_id = str(uuid.uuid4()) +client_id = "entity-backend" + +humbug_consent = HumbugConsent(True) +humbug_reporter = HumbugReporter( + name="entity", + consent=HumbugConsent(True), + client_id=client_id, + session_id=session_id, + bugout_token=ENTITY_HUMBUG_REPORTS_BUGOUT_TOKEN, + tags=[], +) + +humbug_version_tag = f"version:{VERSION}" +humbug_tags = [humbug_version_tag] + + +class ExceptionWithReporting(Exception): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + humbug_reporter.error_report(self, tags=humbug_tags, publish=True) diff --git a/api/entityapi/settings.py b/api/entityapi/settings.py index 81377ca..a9183f6 100644 --- a/api/entityapi/settings.py +++ b/api/entityapi/settings.py @@ -28,3 +28,8 @@ MOONSTREAM_APPLICATION_ID = os.environ.get("MOONSTREAM_APPLICATION_ID", "") if MOONSTREAM_APPLICATION_ID == "": raise ValueError("MOONSTREAM_APPLICATION_ID environment variable must be set") + +# Humbug reporting +ENTITY_HUMBUG_REPORTS_BUGOUT_TOKEN = os.environ.get( + "ENTITY_HUMBUG_REPORTS_BUGOUT_TOKEN" +) diff --git a/api/entityapi/version.txt b/api/entityapi/version.txt index fa3de58..99d85ec 100644 --- a/api/entityapi/version.txt +++ b/api/entityapi/version.txt @@ -1 +1 @@ -0.0.5 \ No newline at end of file +0.0.6 \ No newline at end of file diff --git a/api/setup.py b/api/setup.py index b47b6d1..906e0b4 100644 --- a/api/setup.py +++ b/api/setup.py @@ -14,6 +14,7 @@ install_requires=[ "bugout>=0.2.5", "fastapi", + "humbug>=0.2.7", "psycopg2-binary", "pydantic", "sqlalchemy",