moonstream/moonstreamapi/moonstreamapi/routes/subscriptions.py

562 wiersze
17 KiB
Python
Czysty Zwykły widok Historia

"""
The Moonstream subscriptions HTTP API
"""
2021-11-11 14:37:19 +00:00
import hashlib
2021-11-02 15:40:40 +00:00
import json
2021-12-16 13:26:04 +00:00
import logging
from typing import Any, Dict, List, Optional
2023-04-26 15:04:56 +00:00
from bugout.exceptions import BugoutResponseException
from fastapi import APIRouter, Depends, Request, Form, BackgroundTasks
from web3 import Web3
from ..actions import (
validate_abi_json,
apply_moonworm_tasks,
get_entity_subscription_collection_id,
EntityCollectionNotFoundException,
2023-05-25 14:00:20 +00:00
get_moonworm_jobs,
)
from ..admin import subscription_types
from .. import data
2021-12-16 13:26:04 +00:00
from ..admin import subscription_types
from ..middleware import MoonstreamHTTPException
2021-09-01 12:03:44 +00:00
from ..reporter import reporter
from ..settings import bugout_client as bc, entity_client as ec
2023-05-25 14:00:20 +00:00
from ..settings import MOONSTREAM_ADMIN_ACCESS_TOKEN, MOONSTREAM_MOONWORM_TASKS_JOURNAL
from ..web3_provider import yield_web3_provider
logger = logging.getLogger(__name__)
router = APIRouter(
prefix="/subscriptions",
)
BUGOUT_RESOURCE_TYPE_SUBSCRIPTION = "subscription"
2023-02-08 21:30:45 +00:00
BUGOUT_RESOURCE_TYPE_ENTITY_SUBSCRIPTION = "entity_subscription"
@router.post("/", tags=["subscriptions"], response_model=data.SubscriptionResourceData)
async def add_subscription_handler(
request: Request,
background_tasks: BackgroundTasks,
2021-07-27 16:51:32 +00:00
address: str = Form(...),
color: str = Form(...),
label: str = Form(...),
subscription_type_id: str = Form(...),
2021-10-26 15:26:03 +00:00
abi: Optional[str] = Form(None),
web3: Web3 = Depends(yield_web3_provider),
2021-07-27 15:08:40 +00:00
) -> data.SubscriptionResourceData:
"""
Add subscription to blockchain stream data for user.
"""
token = request.state.token
2021-07-27 15:08:40 +00:00
2021-11-04 13:03:18 +00:00
if subscription_type_id != "ethereum_whalewatch":
try:
2021-11-04 13:24:56 +00:00
address = web3.toChecksumAddress(address)
2021-11-04 13:03:18 +00:00
except ValueError as e:
raise MoonstreamHTTPException(
status_code=400,
detail=str(e),
internal_error=e,
)
except Exception as e:
logger.error(f"Failed to convert address to checksum address")
raise MoonstreamHTTPException(
status_code=500,
internal_error=e,
detail="Currently unable to convert address to checksum address",
)
else:
raise MoonstreamHTTPException(
status_code=400,
detail="Currently ethereum_whalewatch not supported",
)
active_subscription_types_response = subscription_types.list_subscription_types(
active_only=True
)
available_subscription_type_ids = [
subscription_type.resource_data.get("id")
for subscription_type in active_subscription_types_response.resources
if subscription_type.resource_data.get("id") is not None
2021-07-27 15:08:40 +00:00
]
if subscription_type_id not in available_subscription_type_ids:
raise MoonstreamHTTPException(
status_code=404,
detail=f"Invalid subscription type: {subscription_type_id}.",
2021-07-27 15:08:40 +00:00
)
user = request.state.user
2021-07-27 15:08:40 +00:00
2023-02-09 20:29:33 +00:00
content: Dict[str, Any] = {}
2021-10-26 15:26:03 +00:00
if abi:
2021-11-11 14:37:19 +00:00
try:
json_abi = json.loads(abi)
except json.JSONDecodeError:
raise MoonstreamHTTPException(status_code=400, detail="Malformed abi body.")
validate_abi_json(json_abi)
2021-11-02 15:40:40 +00:00
2021-11-11 14:37:19 +00:00
abi_string = json.dumps(json_abi, sort_keys=True, indent=2)
hash = hashlib.md5(abi_string.encode("utf-8")).hexdigest()
content["abi"] = abi
content["abi_hash"] = hash
2021-10-26 15:26:03 +00:00
background_tasks.add_task(
apply_moonworm_tasks,
subscription_type_id,
json_abi,
address,
)
try:
collection_id = get_entity_subscription_collection_id(
2023-02-08 21:30:45 +00:00
resource_type=BUGOUT_RESOURCE_TYPE_ENTITY_SUBSCRIPTION,
token=MOONSTREAM_ADMIN_ACCESS_TOKEN,
user_id=user.id,
create_if_not_exist=True,
)
entity = ec.add_entity(
token=token,
collection_id=collection_id,
address=address,
blockchain=subscription_types.CANONICAL_SUBSCRIPTION_TYPES[
subscription_type_id
].blockchain,
name=label,
required_fields=[
2023-02-08 21:30:45 +00:00
{"type": "subscription"},
{"subscription_type_id": f"{subscription_type_id}"},
{"color": f"{color}"},
{"label": f"{label}"},
{"user_id": f"{user.id}"},
],
2023-02-08 21:30:45 +00:00
secondary_fields=content,
)
except EntityCollectionNotFoundException as e:
raise MoonstreamHTTPException(
status_code=404,
detail="User subscriptions collection not found",
internal_error=e,
)
except Exception as e:
logger.error(f"Failed to get collection id")
raise MoonstreamHTTPException(
status_code=500,
internal_error=e,
detail="Currently unable to get collection id",
)
2023-02-08 21:30:45 +00:00
return data.SubscriptionResourceData(
id=str(entity.entity_id),
user_id=str(user.id),
address=address,
color=color,
label=label,
abi=entity.secondary_fields.get("abi"),
subscription_type_id=subscription_type_id,
updated_at=entity.updated_at,
created_at=entity.created_at,
)
@router.delete(
2021-07-27 16:51:32 +00:00
"/{subscription_id}",
tags=["subscriptions"],
response_model=data.SubscriptionResourceData,
)
async def delete_subscription_handler(request: Request, subscription_id: str):
"""
Delete subscriptions.
"""
token = request.state.token
user = request.state.user
2021-07-27 16:51:32 +00:00
try:
collection_id = get_entity_subscription_collection_id(
resource_type=BUGOUT_RESOURCE_TYPE_ENTITY_SUBSCRIPTION,
token=MOONSTREAM_ADMIN_ACCESS_TOKEN,
user_id=user.id,
)
deleted_entity = ec.delete_entity(
token=token,
collection_id=collection_id,
entity_id=subscription_id,
)
except EntityCollectionNotFoundException as e:
raise MoonstreamHTTPException(
status_code=404,
detail="User subscriptions collection not found",
internal_error=e,
)
2021-07-27 16:51:32 +00:00
except Exception as e:
logger.error(f"Failed to delete subscription")
raise MoonstreamHTTPException(
status_code=500,
detail="Internal error",
)
tags = deleted_entity.required_fields
subscription_type_id = None
color = None
label = None
abi = None
if tags is not None:
for tag in tags:
if "subscription_type_id" in tag:
subscription_type_id = tag["subscription_type_id"]
if "color" in tag:
color = tag["color"]
if "label" in tag:
label = tag["label"]
if deleted_entity.secondary_fields is not None:
abi = deleted_entity.secondary_fields.get("abi")
2021-07-27 16:51:32 +00:00
return data.SubscriptionResourceData(
id=str(deleted_entity.entity_id),
user_id=str(user.id),
address=deleted_entity.address,
color=color,
label=label,
abi=abi,
subscription_type_id=subscription_type_id,
updated_at=deleted_entity.updated_at,
created_at=deleted_entity.created_at,
2021-07-27 16:51:32 +00:00
)
@router.get("/", tags=["subscriptions"], response_model=data.SubscriptionsListResponse)
2023-05-02 12:04:46 +00:00
async def get_subscriptions_handler(
request: Request,
limit: Optional[int] = 10,
offset: Optional[int] = 0,
) -> data.SubscriptionsListResponse:
"""
Get user's subscriptions.
"""
token = request.state.token
user = request.state.user
try:
collection_id = get_entity_subscription_collection_id(
resource_type=BUGOUT_RESOURCE_TYPE_ENTITY_SUBSCRIPTION,
token=MOONSTREAM_ADMIN_ACCESS_TOKEN,
user_id=user.id,
2023-05-04 15:30:25 +00:00
create_if_not_exist=True,
)
subscriprions_list = ec.search_entities(
token=token,
collection_id=collection_id,
required_field=[f"type:subscription"],
2023-05-02 12:04:46 +00:00
limit=limit,
offset=offset,
)
except EntityCollectionNotFoundException as e:
raise MoonstreamHTTPException(
status_code=404,
detail="User subscriptions collection not found",
internal_error=e,
)
except Exception as e:
logger.error(
f"Error listing subscriptions for user ({user.id}) with token ({token}), error: {str(e)}"
)
2021-09-01 12:03:44 +00:00
reporter.error_report(e)
raise MoonstreamHTTPException(status_code=500, internal_error=e)
2021-07-27 15:08:40 +00:00
subscriptions = []
for subscription in subscriprions_list.entities:
tags = subscription.required_fields
label, color, subscription_type_id = None, None, None
for tag in tags:
if "subscription_type_id" in tag:
subscription_type_id = tag["subscription_type_id"]
if "color" in tag:
color = tag["color"]
if "label" in tag:
label = tag["label"]
subscriptions.append(
2021-07-27 15:08:40 +00:00
data.SubscriptionResourceData(
id=str(subscription.entity_id),
user_id=str(user.id),
address=subscription.address,
color=color,
label=label,
abi="True" if subscription.secondary_fields.get("abi") else None,
subscription_type_id=subscription_type_id,
updated_at=subscription.updated_at,
created_at=subscription.created_at,
)
)
return data.SubscriptionsListResponse(subscriptions=subscriptions)
2021-07-27 15:08:40 +00:00
@router.put(
2021-08-10 16:29:16 +00:00
"/{subscription_id}",
tags=["subscriptions"],
response_model=data.SubscriptionResourceData,
)
async def update_subscriptions_handler(
request: Request,
subscription_id: str,
background_tasks: BackgroundTasks,
2021-08-10 16:29:16 +00:00
color: Optional[str] = Form(None),
label: Optional[str] = Form(None),
2021-11-08 12:24:11 +00:00
abi: Optional[str] = Form(None),
2021-08-10 16:29:16 +00:00
) -> data.SubscriptionResourceData:
"""
Get user's subscriptions.
"""
token = request.state.token
user = request.state.user
update_required_fields = []
update_secondary_fields = {}
try:
collection_id = get_entity_subscription_collection_id(
resource_type=BUGOUT_RESOURCE_TYPE_ENTITY_SUBSCRIPTION,
token=MOONSTREAM_ADMIN_ACCESS_TOKEN,
user_id=user.id,
)
# get subscription entity
subscription_entity = ec.get_entity(
token=token,
collection_id=collection_id,
entity_id=subscription_id,
)
subscription_type_id = None
update_required_fields = subscription_entity.required_fields
for field in update_required_fields:
if "subscription_type_id" in field:
subscription_type_id = field["subscription_type_id"]
if not subscription_type_id:
logger.error(
f"Subscription entity {subscription_id} in collection {collection_id} has no subscription_type_id malformed subscription entity"
)
raise MoonstreamHTTPException(
status_code=404,
detail="Not valid subscription entity",
)
except EntityCollectionNotFoundException as e:
raise MoonstreamHTTPException(
status_code=404,
detail="User subscriptions collection not found",
internal_error=e,
)
except Exception as e:
logger.error(
f"Error get subscriptions for user ({user.id}) with token ({token}), error: {str(e)}"
)
raise MoonstreamHTTPException(status_code=500, internal_error=e)
for field in update_required_fields:
if "color" in field and color is not None:
field["color"] = color
2021-08-10 16:29:16 +00:00
if "label" in field and label is not None:
field["label"] = label
2021-08-10 16:29:16 +00:00
2021-11-08 12:24:11 +00:00
if abi:
2021-11-11 14:37:19 +00:00
try:
json_abi = json.loads(abi)
except json.JSONDecodeError:
raise MoonstreamHTTPException(status_code=400, detail="Malformed abi body.")
validate_abi_json(json_abi)
abi_string = json.dumps(json_abi, sort_keys=True, indent=2)
update_secondary_fields["abi"] = abi_string
hash = hashlib.md5(abi_string.encode("utf-8")).hexdigest()
2021-11-08 12:24:11 +00:00
update_secondary_fields["abi_hash"] = hash
else:
update_secondary_fields = subscription_entity.secondary_fields
2021-11-11 14:37:19 +00:00
2021-08-10 16:29:16 +00:00
try:
subscription = ec.update_entity(
2021-08-10 16:29:16 +00:00
token=token,
collection_id=collection_id,
entity_id=subscription_id,
address=subscription_entity.address,
blockchain=subscription_entity.blockchain,
name=subscription_entity.name,
required_fields=update_required_fields,
secondary_fields=update_secondary_fields,
2021-08-10 16:29:16 +00:00
)
2021-08-10 16:29:16 +00:00
except Exception as e:
2023-04-26 14:18:56 +00:00
logger.error(f"Error update user subscriptions: {str(e)}")
raise MoonstreamHTTPException(status_code=500, internal_error=e)
2021-08-10 16:29:16 +00:00
if abi:
background_tasks.add_task(
apply_moonworm_tasks,
subscription_type_id,
json_abi,
subscription.address,
)
2021-08-10 16:29:16 +00:00
return data.SubscriptionResourceData(
id=str(subscription.entity_id),
user_id=str(user.id),
address=subscription.address,
color=color,
label=label,
abi=subscription.secondary_fields.get("abi"),
subscription_type_id=subscription_type_id,
updated_at=subscription_entity.updated_at,
created_at=subscription_entity.created_at,
2021-08-10 16:29:16 +00:00
)
2021-11-29 12:53:09 +00:00
@router.get(
"/{subscription_id}/abi",
tags=["subscriptions"],
response_model=data.SubdcriptionsAbiResponse,
)
async def get_subscription_abi_handler(
request: Request,
subscription_id: str,
2021-11-29 13:34:24 +00:00
) -> data.SubdcriptionsAbiResponse:
2021-11-29 12:53:09 +00:00
token = request.state.token
user = request.state.user
2021-11-29 12:53:09 +00:00
try:
collection_id = get_entity_subscription_collection_id(
resource_type=BUGOUT_RESOURCE_TYPE_ENTITY_SUBSCRIPTION,
token=MOONSTREAM_ADMIN_ACCESS_TOKEN,
user_id=user.id,
)
# get subscription entity
subscription_resource = ec.get_entity(
token=token,
collection_id=collection_id,
entity_id=subscription_id,
2021-11-29 12:53:09 +00:00
)
except EntityCollectionNotFoundException as e:
2021-11-29 12:53:09 +00:00
raise MoonstreamHTTPException(
status_code=404,
detail="User subscriptions collection not found",
internal_error=e,
)
except Exception as e:
logger.error(
f"Error get subscriptions for user ({user}) with token ({token}), error: {str(e)}"
2021-11-29 12:53:09 +00:00
)
raise MoonstreamHTTPException(status_code=500, internal_error=e)
2021-11-29 12:53:09 +00:00
if "abi" not in subscription_resource.secondary_fields.keys():
raise MoonstreamHTTPException(status_code=404, detail="Abi not found")
2021-11-29 12:53:09 +00:00
return data.SubdcriptionsAbiResponse(
abi=subscription_resource.secondary_fields["abi"]
)
2021-11-29 12:53:09 +00:00
2023-05-25 14:00:20 +00:00
@router.get(
"/{subscription_id}/jobs",
tags=["subscriptions"],
response_model=data.SubdcriptionsAbiResponse,
)
async def get_subscription_jobs_handler(
request: Request,
subscription_id: str,
) -> Any:
token = request.state.token
user = request.state.user
try:
collection_id = get_entity_subscription_collection_id(
resource_type=BUGOUT_RESOURCE_TYPE_ENTITY_SUBSCRIPTION,
token=MOONSTREAM_ADMIN_ACCESS_TOKEN,
user_id=user.id,
)
# get subscription entity
subscription_resource = ec.get_entity(
token=token,
collection_id=collection_id,
entity_id=subscription_id,
)
except EntityCollectionNotFoundException as e:
raise MoonstreamHTTPException(
status_code=404,
detail="User subscriptions collection not found",
internal_error=e,
)
except Exception as e:
logger.error(
f"Error get subscriptions for user ({user}) with token ({token}), error: {str(e)}"
)
raise MoonstreamHTTPException(status_code=500, internal_error=e)
for field in subscription_resource.required_fields:
if "subscription_type_id" in field:
subscription_type_id = field["subscription_type_id"]
if "address" in field:
subscription_address = field["address"]
get_moonworm_jobs_response = get_moonworm_jobs(
subscription_type_id=subscription_type_id,
address=subscription_address,
)
return get_moonworm_jobs_response
@router.get(
"/types", tags=["subscriptions"], response_model=data.SubscriptionTypesListResponse
2021-07-27 15:08:40 +00:00
)
async def list_subscription_types() -> data.SubscriptionTypesListResponse:
2021-07-27 15:08:40 +00:00
"""
Get availables subscription types.
2021-07-27 15:08:40 +00:00
"""
results: List[data.SubscriptionTypeResourceData] = []
2021-07-27 15:08:40 +00:00
try:
response = subscription_types.list_subscription_types()
results = [
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)
2021-07-27 15:08:40 +00:00
except Exception as e:
2021-09-01 12:03:44 +00:00
logger.error(f"Error reading subscription types from Brood API: {str(e)}")
raise MoonstreamHTTPException(status_code=500, internal_error=e)
return data.SubscriptionTypesListResponse(subscription_types=results)