""" The Moonstream subscriptions HTTP API """ import logging from typing import Any, Dict, List, Optional from bugout.data import BugoutResource from fastapi import APIRouter, Depends, Query, Request from sqlalchemy.orm import Session from moonstreamdb import db from .. import data, stream_queries from ..middleware import MoonstreamHTTPException from ..providers import ( ReceivingEventsException, event_providers, get_events, latest_events, next_event, previous_event, ) from ..settings import ( BUGOUT_REQUEST_TIMEOUT_SECONDS, MOONSTREAM_ADMIN_ACCESS_TOKEN, MOONSTREAM_DATA_JOURNAL_ID, ) from ..settings import bugout_client as bc from .subscriptions import BUGOUT_RESOURCE_TYPE_SUBSCRIPTION logger = logging.getLogger(__name__) router = APIRouter( prefix="/streams", ) def get_user_subscriptions(token: str) -> Dict[str, List[BugoutResource]]: """ Returns the given user's subscriptions grouped by subscription type. """ response = bc.list_resources( token=token, params={ "type": BUGOUT_RESOURCE_TYPE_SUBSCRIPTION, }, timeout=BUGOUT_REQUEST_TIMEOUT_SECONDS, ) # TODO(andrey, kompotkot, zomglings): PAGINATION!!! user_subscriptions: Dict[str, List[BugoutResource]] = {} for subscription in response.resources: subscription_type = subscription.resource_data.get("subscription_type_id") if subscription_type is None: continue if user_subscriptions.get(subscription_type) is None: user_subscriptions[subscription_type] = [] user_subscriptions[subscription_type].append(subscription) return user_subscriptions @router.get("/info", tags=["streams"]) async def info_handler() -> Dict[str, Any]: info = { event_type: { "description": provider.description, "default_time_interval_seconds": provider.default_time_interval_seconds, "estimated_events_per_time_interval": provider.estimated_events_per_time_interval, } for event_type, provider in event_providers.items() } return info @router.get("/", tags=["streams"], response_model=data.GetEventsResponse) async def stream_handler( request: Request, q: str = Query(""), start_time: int = Query(0), end_time: Optional[int] = Query(None), include_start: bool = Query(False), include_end: bool = Query(False), db_session: Session = Depends(db.yield_db_session), ) -> data.GetEventsResponse: """ Gets all events in the client's stream subject to the constraints defined by the following query parameters: - q: Query string which filters over subscriptions - start_time, end_time, include_start, include_end: These define the window of time from which we want to retrieve events. All times must be given as seconds since the Unix epoch. """ stream_boundary = data.StreamBoundary( start_time=start_time, end_time=end_time, include_start=include_start, include_end=include_end, ) user_subscriptions = get_user_subscriptions(request.state.token) query = stream_queries.StreamQuery( subscription_types=[subtype for subtype in event_providers], subscriptions=[] ) if q.strip() != "": query = stream_queries.parse_query_string(q) try: _, events = get_events( db_session, bc, MOONSTREAM_DATA_JOURNAL_ID, MOONSTREAM_ADMIN_ACCESS_TOKEN, stream_boundary, query, user_subscriptions, result_timeout=10.0, raise_on_error=True, ) except ReceivingEventsException as e: logger.error("Error receiving events from provider") raise MoonstreamHTTPException(status_code=500, internal_error=e) except Exception as e: logger.error("Unable to get events") raise MoonstreamHTTPException(status_code=500, internal_error=e) response = data.GetEventsResponse(stream_boundary=stream_boundary, events=events) return response @router.get("/latest", tags=["streams"]) async def latest_events_handler( request: Request, q=Query(""), db_session: Session = Depends(db.yield_db_session) ) -> List[data.Event]: """ Gets the latest events in the client's stream subject to the constraints defined by the following query parameters: - q: Query string which filters over subscriptions All times must be given as seconds since the Unix epoch. """ user_subscriptions = get_user_subscriptions(request.state.token) query = stream_queries.StreamQuery( subscription_types=[subtype for subtype in event_providers], subscriptions=[] ) if q.strip() != "": query = stream_queries.parse_query_string(q) try: events = latest_events( db_session, bc, MOONSTREAM_DATA_JOURNAL_ID, MOONSTREAM_ADMIN_ACCESS_TOKEN, query, 1, user_subscriptions, result_timeout=6.0, raise_on_error=True, sort_events=True, ) except ReceivingEventsException as e: logger.error("Error receiving events from provider") raise MoonstreamHTTPException(status_code=500, internal_error=e) except Exception as e: logger.error("Unable to get latest events") raise MoonstreamHTTPException(status_code=500, internal_error=e) return events @router.get("/next", tags=["stream"]) async def next_event_handler( request: Request, q: str = Query(""), start_time: int = Query(0), end_time: Optional[int] = Query(None), include_start: bool = Query(False), include_end: bool = Query(False), db_session: Session = Depends(db.yield_db_session), ) -> Optional[data.Event]: """ Gets the next event in the client's stream subject to the constraints defined by the following query parameters: - q: Query string which filters over subscriptions - start_time, end_time, include_start, include_end: These define the window of time after which we want to retrieve the next event. All times must be given as seconds since the Unix epoch. """ stream_boundary = data.StreamBoundary( start_time=start_time, end_time=end_time, include_start=include_start, include_end=include_end, ) user_subscriptions = get_user_subscriptions(request.state.token) query = stream_queries.StreamQuery( subscription_types=[subtype for subtype in event_providers], subscriptions=[] ) if q.strip() != "": query = stream_queries.parse_query_string(q) try: event = next_event( db_session, bc, MOONSTREAM_DATA_JOURNAL_ID, MOONSTREAM_ADMIN_ACCESS_TOKEN, stream_boundary, query, user_subscriptions, result_timeout=6.0, raise_on_error=True, ) except ReceivingEventsException as e: logger.error("Error receiving events from provider") raise MoonstreamHTTPException(status_code=500, internal_error=e) except Exception as e: logger.error("Unable to get next events") raise MoonstreamHTTPException(status_code=500, internal_error=e) return event @router.get("/previous", tags=["stream"]) async def previous_event_handler( request: Request, q: str = Query(""), start_time: int = Query(0), end_time: Optional[int] = Query(None), include_start: bool = Query(False), include_end: bool = Query(False), db_session: Session = Depends(db.yield_db_session), ) -> Optional[data.Event]: """ Gets the previous event in the client's stream subject to the constraints defined by the following query parameters: - q: Query string which filters over subscriptions - start_time, end_time, include_start, include_end: These define the window of time before which we want to retrieve the previous event. All times must be given as seconds since the Unix epoch. """ stream_boundary = data.StreamBoundary( start_time=start_time, end_time=end_time, include_start=include_start, include_end=include_end, ) user_subscriptions = get_user_subscriptions(request.state.token) query = stream_queries.StreamQuery( subscription_types=[subtype for subtype in event_providers], subscriptions=[] ) if q.strip() != "": query = stream_queries.parse_query_string(q) try: event = previous_event( db_session, bc, MOONSTREAM_DATA_JOURNAL_ID, MOONSTREAM_ADMIN_ACCESS_TOKEN, stream_boundary, query, user_subscriptions, result_timeout=6.0, raise_on_error=True, ) except ReceivingEventsException as e: logger.error("Error receiving events from provider") raise MoonstreamHTTPException(status_code=500, internal_error=e) except Exception as e: logger.error("Unable to get previous events") raise MoonstreamHTTPException(status_code=500, internal_error=e) return event