From 1c878329b0e3089880e99d1596e079be2c5939bb Mon Sep 17 00:00:00 2001 From: kompotkot Date: Thu, 15 Aug 2024 12:36:57 +0000 Subject: [PATCH] List metatx requesters with contracts and call requests calculated --- engineapi/engineapi/contracts_actions.py | 65 ++++++++++++++++++- engineapi/engineapi/data.py | 6 ++ engineapi/engineapi/routes/metatx.py | 36 +++++++++- .../engineapi/scripts/metatx_resources.py | 6 -- engineapi/engineapi/settings.py | 2 + 5 files changed, 106 insertions(+), 9 deletions(-) diff --git a/engineapi/engineapi/contracts_actions.py b/engineapi/engineapi/contracts_actions.py index 04fa0f29..8715471b 100644 --- a/engineapi/engineapi/contracts_actions.py +++ b/engineapi/engineapi/contracts_actions.py @@ -6,7 +6,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed from datetime import datetime, timedelta from typing import Any, Dict, List, Optional, Set, Tuple -from bugout.data import BugoutResourceHolder, BugoutResourceHolders, HolderType +from bugout.data import BugoutResourceHolder, HolderType from sqlalchemy import func, or_, text, tuple_ from sqlalchemy.dialects.postgresql import insert from sqlalchemy.engine import Row @@ -23,6 +23,7 @@ from .models import ( RegisteredContract, ) from .settings import ( + METATX_REQUESTER_TYPE, MOONSTREAM_ADMIN_ACCESS_TOKEN, MOONSTREAM_APPLICATION_ID, bugout_client, @@ -186,7 +187,7 @@ def validate_method_and_params( def create_resource_for_registered_contract(user_id: uuid.UUID) -> uuid.UUID: - resource_data = {"type": "metatx_requester"} + resource_data = {"type": METATX_REQUESTER_TYPE} creator_permissions = ["create", "read", "update", "delete"] resource = bugout_client.create_resource( @@ -847,6 +848,66 @@ def fetch_metatx_requester_ids(token: uuid.UUID) -> List[uuid.UUID]: return metatx_requester_ids +def count_contracts_and_requests_for_requester( + db_session: Session, metatx_requester_ids: List[uuid.UUID] +) -> List[data.MetatxRequestersResponse]: + registered_contracts_subquery = ( + db_session.query( + RegisteredContract.metatx_requester_id, + func.count(RegisteredContract.id).label("registered_contracts_count"), + ) + .filter(RegisteredContract.metatx_requester_id.in_(metatx_requester_ids)) + .group_by(RegisteredContract.metatx_requester_id) + .subquery() + ) + call_requests_subquery = ( + db_session.query( + CallRequest.metatx_requester_id, + func.count(CallRequest.id).label("call_requests_count"), + ) + .filter(CallRequest.metatx_requester_id.in_(metatx_requester_ids)) + .group_by(CallRequest.metatx_requester_id) + .subquery() + ) + + query = db_session.query( + registered_contracts_subquery.c.metatx_requester_id, + registered_contracts_subquery.c.registered_contracts_count, + call_requests_subquery.c.call_requests_count, + ).outerjoin( + call_requests_subquery, + registered_contracts_subquery.c.metatx_requester_id + == call_requests_subquery.c.metatx_requester_id, + ) + + results = query.all() + + metatx_requesters: List[data.MetatxRequestersResponse] = [] + for r in results: + metatx_requester_id = r[0] + metatx_requesters.append( + data.MetatxRequestersResponse( + metatx_requester_id=metatx_requester_id, + registered_contracts_count=r[1], + call_requests_count=r[2], + ) + ) + + metatx_requester_ids.remove(metatx_requester_id) + + # Add empty metatx requesters + for r_id in metatx_requester_ids: + metatx_requesters.append( + data.MetatxRequestersResponse( + metatx_requester_id=r_id, + registered_contracts_count=0, + call_requests_count=0, + ) + ) + + return metatx_requesters + + def handle_register(args: argparse.Namespace) -> None: """ Handles the register command. diff --git a/engineapi/engineapi/data.py b/engineapi/engineapi/data.py index f7819bfc..71e3b37d 100644 --- a/engineapi/engineapi/data.py +++ b/engineapi/engineapi/data.py @@ -278,6 +278,12 @@ class RegisteredContractWithHoldersResponse(RegisteredContractResponse): holders: List[RegisteredContractHolderResponse] = Field(default_factory=list) +class MetatxRequestersResponse(BaseModel): + metatx_requester_id: UUID + registered_contracts_count: int + call_requests_count: int + + class CallSpecification(BaseModel): caller: str method: str diff --git a/engineapi/engineapi/routes/metatx.py b/engineapi/engineapi/routes/metatx.py index 825fc2ef..4ae631f7 100644 --- a/engineapi/engineapi/routes/metatx.py +++ b/engineapi/engineapi/routes/metatx.py @@ -11,7 +11,7 @@ from typing import Dict, List, Optional, Set, Tuple from uuid import UUID from bugout.data import BugoutResourceHolder, BugoutUser -from fastapi import BackgroundTasks, Body, Depends, FastAPI, Form, Path, Query, Request +from fastapi import BackgroundTasks, Body, Depends, FastAPI, Path, Query, Request from sqlalchemy.exc import NoResultFound from sqlalchemy.orm import Session from web3 import Web3 @@ -40,6 +40,10 @@ tags_metadata = [ "description": DESCRIPTION, }, {"name": "requests", "description": "Call requests for registered contracts."}, + { + "name": "requesters", + "description": "Metatx requester represented by resource for registered contracts access.", + }, ] @@ -304,6 +308,36 @@ async def delete_contract_route( return parsed_registered_contract +@app.get( + "/requesters", + tags=["requesters"], + response_model=List[data.MetatxRequestersResponse], +) +async def list_metatx_requesters_route( + user_authorization: Tuple[BugoutUser, UUID] = Depends(request_user_auth), + db_session: Session = Depends(db.yield_db_read_only_session), +) -> List[data.MetatxRequestersResponse]: + """ + Get list of metatx requesters available to user. + + Helps to track number of empty metatx requesters without registered contracts and call requests. + """ + _, token = user_authorization + + try: + metatx_requester_ids = contracts_actions.fetch_metatx_requester_ids(token=token) + metatx_requesters = ( + contracts_actions.count_contracts_and_requests_for_requester( + db_session=db_session, metatx_requester_ids=metatx_requester_ids + ) + ) + except Exception as err: + logger.error(repr(err)) + raise EngineHTTPException(status_code=500) + + return metatx_requesters + + @app.get( "/contracts/{contract_id}/holders", tags=["contracts"], diff --git a/engineapi/engineapi/scripts/metatx_resources.py b/engineapi/engineapi/scripts/metatx_resources.py index d9c5babc..55256ea8 100644 --- a/engineapi/engineapi/scripts/metatx_resources.py +++ b/engineapi/engineapi/scripts/metatx_resources.py @@ -3,16 +3,10 @@ import sys import time from typing import Any, Dict, List, Optional -from bugout.data import BugoutResourceHolder from sqlalchemy.sql import delete, distinct, func, insert, update from .. import db, models from ..contracts_actions import create_resource_for_registered_contract -from ..settings import ( - MOONSTREAM_ADMIN_ACCESS_TOKEN, - MOONSTREAM_APPLICATION_ID, - bugout_client, -) def generate_handler(args: argparse.Namespace): diff --git a/engineapi/engineapi/settings.py b/engineapi/engineapi/settings.py index ffe92e6d..fb8923ac 100644 --- a/engineapi/engineapi/settings.py +++ b/engineapi/engineapi/settings.py @@ -21,6 +21,8 @@ bugout_client = Bugout(brood_api_url=BUGOUT_BROOD_URL, spire_api_url=BUGOUT_SPIR BUGOUT_MAX_CONCURRENT_REQUESTS = 4 bugout_client_semaphore = threading.Semaphore(BUGOUT_MAX_CONCURRENT_REQUESTS) +METATX_REQUESTER_TYPE = "metatx_requester" + ENGINE_DEV_RAW = os.environ.get("ENGINE_DEV", "") ENGINE_DEV = True if ENGINE_DEV_RAW in {"1", "true", "yes", "t", "y"} else False