From 1a282c5811279349858f627e76796ac53b46bdb4 Mon Sep 17 00:00:00 2001 From: kompotkot Date: Tue, 3 Oct 2023 10:07:37 +0000 Subject: [PATCH 1/7] live_at for call_requests --- .../4f05d212ea49_live_at_for_metatx.py | 28 +++++++++++++++++++ engineapi/engineapi/models.py | 1 + 2 files changed, 29 insertions(+) create mode 100644 engineapi/alembic/versions/4f05d212ea49_live_at_for_metatx.py diff --git a/engineapi/alembic/versions/4f05d212ea49_live_at_for_metatx.py b/engineapi/alembic/versions/4f05d212ea49_live_at_for_metatx.py new file mode 100644 index 00000000..04224d0d --- /dev/null +++ b/engineapi/alembic/versions/4f05d212ea49_live_at_for_metatx.py @@ -0,0 +1,28 @@ +"""Live at for metatx + +Revision ID: 4f05d212ea49 +Revises: 040f2dfde5a5 +Create Date: 2023-10-03 10:00:09.730620 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '4f05d212ea49' +down_revision = '040f2dfde5a5' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('call_requests', sa.Column('live_at', sa.DateTime(timezone=True), server_default=sa.text("TIMEZONE('utc', statement_timestamp())"), nullable=False)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('call_requests', 'live_at') + # ### end Alembic commands ### diff --git a/engineapi/engineapi/models.py b/engineapi/engineapi/models.py index 36cc975d..10d2e357 100644 --- a/engineapi/engineapi/models.py +++ b/engineapi/engineapi/models.py @@ -315,6 +315,7 @@ class CallRequest(Base): parameters = Column(JSONB, nullable=False) expires_at = Column(DateTime(timezone=True), nullable=True, index=True) + live_at = Column(DateTime(timezone=True), server_default=utcnow(), nullable=False) created_at = Column( DateTime(timezone=True), server_default=utcnow(), nullable=False From a46afe65d781b2cc4005590642ae73def63332bd Mon Sep 17 00:00:00 2001 From: kompotkot Date: Tue, 3 Oct 2023 15:47:58 +0000 Subject: [PATCH 2/7] Operations with live_at for call_requests --- engineapi/engineapi/contracts_actions.py | 31 +++++++++++++-- engineapi/engineapi/data.py | 2 + engineapi/engineapi/middleware.py | 8 ++-- engineapi/engineapi/routes/metatx.py | 49 +++++++++++++++++++++--- 4 files changed, 77 insertions(+), 13 deletions(-) diff --git a/engineapi/engineapi/contracts_actions.py b/engineapi/engineapi/contracts_actions.py index e5a632c1..64c067e4 100644 --- a/engineapi/engineapi/contracts_actions.py +++ b/engineapi/engineapi/contracts_actions.py @@ -2,7 +2,7 @@ import argparse import json import logging import uuid -from datetime import timedelta +from datetime import datetime, timedelta from typing import Any, Dict, List, Optional, Tuple from sqlalchemy import func, text @@ -101,6 +101,7 @@ def parse_call_request_response( request_id=str(obj[0].request_id), parameters=obj[0].parameters, expires_at=obj[0].expires_at, + live_at=obj[0].live_at, created_at=obj[0].created_at, updated_at=obj[0].updated_at, ) @@ -326,13 +327,14 @@ def delete_registered_contract( return (registered_contract, blockchain) -def request_calls( +def create_request_calls( db_session: Session, metatx_requester_id: uuid.UUID, registered_contract_id: Optional[uuid.UUID], contract_address: Optional[str], call_specs: List[data.CallSpecification], ttl_days: Optional[int] = None, + live_at: Optional[int] = None, ) -> int: """ Batch creates call requests for the given registered contract. @@ -350,6 +352,11 @@ def request_calls( if ttl_days <= 0: raise ValueError("ttl_days must be positive") + if live_at is not None: + assert live_at == int(live_at) + if live_at <= 0: + raise ValueError("live_at must be positive") + # Check that the moonstream_user_id matches a RegisteredContract with the given id or address query = db_session.query(RegisteredContract).filter( RegisteredContract.metatx_requester_id == metatx_requester_id @@ -406,6 +413,7 @@ def request_calls( request_id=specification.request_id, parameters=specification.parameters, expires_at=expires_at, + live_at=datetime.fromtimestamp(live_at), ) db_session.add(request) @@ -472,6 +480,8 @@ def list_call_requests( limit: int = 10, offset: Optional[int] = None, show_expired: bool = False, + show_before_live_at: bool = False, + metatx_requester_id: Optional[uuid.UUID] = None, ) -> List[Row[Tuple[CallRequest, RegisteredContract, CallRequestType]]]: """ List call requests for the given moonstream_user_id @@ -507,6 +517,21 @@ def list_call_requests( CallRequest.expires_at > func.now(), ) + # If user id not specified, do not show call_requests before live_at. + # Otherwise check show_before_live_at argument from query parameter + if metatx_requester_id is not None: + query = query.filter( + CallRequest.metatx_requester_id == metatx_requester_id, + ) + if not show_before_live_at: + query = query.filter( + CallRequest.live_at < func.now(), + ) + else: + query = query.filter( + CallRequest.live_at < func.now(), + ) + if offset is not None: query = query.offset(offset) @@ -633,7 +658,7 @@ def handle_request_calls(args: argparse.Namespace) -> None: try: with db.yield_db_session_ctx() as db_session: - request_calls( + create_request_calls( db_session=db_session, moonstream_user_id=args.moonstream_user_id, registered_contract_id=args.registered_contract_id, diff --git a/engineapi/engineapi/data.py b/engineapi/engineapi/data.py index 8a33d619..5fa20255 100644 --- a/engineapi/engineapi/data.py +++ b/engineapi/engineapi/data.py @@ -284,6 +284,7 @@ class CreateCallRequestsAPIRequest(BaseModel): contract_address: Optional[str] = None specifications: List[CallSpecification] = Field(default_factory=list) ttl_days: Optional[int] = None + live_at: Optional[int] = None # Solution found thanks to https://github.com/pydantic/pydantic/issues/506 @root_validator @@ -306,6 +307,7 @@ class CallRequestResponse(BaseModel): request_id: str parameters: Dict[str, Any] expires_at: Optional[datetime] = None + live_at: datetime created_at: datetime updated_at: datetime diff --git a/engineapi/engineapi/middleware.py b/engineapi/engineapi/middleware.py index f2bb83b0..1b533c4b 100644 --- a/engineapi/engineapi/middleware.py +++ b/engineapi/engineapi/middleware.py @@ -26,8 +26,8 @@ from .settings import ( BUGOUT_REQUEST_TIMEOUT_SECONDS, BUGOUT_RESOURCE_TYPE_APPLICATION_CONFIG, MOONSTREAM_ADMIN_ACCESS_TOKEN, - MOONSTREAM_APPLICATION_ID, MOONSTREAM_ADMIN_ID, + MOONSTREAM_APPLICATION_ID, ) from .settings import bugout_client as bc @@ -72,12 +72,10 @@ class BroodAuthMiddleware(BaseHTTPMiddleware): try: user: BugoutUser = bc.get_user(user_token) if not user.verified: - logger.info( - f"Attempted journal access by unverified Brood account: {user.id}" - ) + logger.info(f"Attempted access by unverified Brood account: {user.id}") return Response( status_code=403, - content="Only verified accounts can access journals", + content="Only verified accounts can have access", ) if str(user.application_id) != str(MOONSTREAM_APPLICATION_ID): return Response( diff --git a/engineapi/engineapi/routes/metatx.py b/engineapi/engineapi/routes/metatx.py index 3af6bac9..b84129e6 100644 --- a/engineapi/engineapi/routes/metatx.py +++ b/engineapi/engineapi/routes/metatx.py @@ -9,13 +9,20 @@ import logging from typing import Dict, List, Optional from uuid import UUID +from bugout.data import BugoutResource, BugoutResources, BugoutUser +from bugout.exceptions import BugoutResponseException from fastapi import Body, Depends, FastAPI, Path, Query, Request from sqlalchemy.exc import NoResultFound from sqlalchemy.orm import Session from .. import contracts_actions, data, db -from ..middleware import BroodAuthMiddleware, BugoutCORSMiddleware, EngineHTTPException -from ..settings import DOCS_TARGET_PATH +from ..middleware import ( + BroodAuthMiddleware, + BugoutCORSMiddleware, + EngineHTTPException, +) +from ..settings import DOCS_TARGET_PATH, MOONSTREAM_APPLICATION_ID +from ..settings import bugout_client as bc from ..version import VERSION logger = logging.getLogger(__name__) @@ -40,7 +47,7 @@ whitelist_paths = { "/metatx/blockchains": "GET", "/metatx/contracts/types": "GET", "/metatx/requests/types": "GET", - "/metatx/requests": "GET", + "/metatx/requests": "GET", # Controls by custom authentication check } app = FastAPI( @@ -280,12 +287,14 @@ async def call_request_types_route( @app.get("/requests", tags=["requests"], response_model=List[data.CallRequestResponse]) async def list_requests_route( + request: Request, contract_id: Optional[UUID] = Query(None), contract_address: Optional[str] = Query(None), caller: str = Query(...), limit: int = Query(100), offset: Optional[int] = Query(None), - show_expired: Optional[bool] = Query(False), + show_expired: bool = Query(False), + show_before_live_at: bool = Query(False), db_session: Session = Depends(db.yield_db_read_only_session), ) -> List[data.CallRequestResponse]: """ @@ -293,6 +302,33 @@ async def list_requests_route( At least one of `contract_id` or `contract_address` must be provided as query parameters. """ + authorization_header = request.headers.get("authorization") + user: Optional[BugoutUser] = None + if authorization_header is not None: + try: + auth_list = authorization_header.split() + if len(auth_list) != 2: + return EngineHTTPException( + status_code=403, content="Wrong authorization header" + ) + + user = bc.get_user(auth_list[-1]) + if not user.verified: + logger.info(f"Attempted access by unverified Brood account: {user.id}") + return EngineHTTPException( + status_code=403, + content="Only verified accounts can have access", + ) + if str(user.application_id) != str(MOONSTREAM_APPLICATION_ID): + return EngineHTTPException( + status_code=403, content="User does not belong to this application" + ) + except BugoutResponseException as e: + return EngineHTTPException(status_code=e.status_code, content=e.detail) + except Exception as e: + logger.error(f"Error processing Brood response: {str(e)}") + return EngineHTTPException(status_code=500, content="Internal server error") + try: requests = contracts_actions.list_call_requests( db_session=db_session, @@ -302,6 +338,8 @@ async def list_requests_route( limit=limit, offset=offset, show_expired=show_expired, + show_before_live_at=show_before_live_at, + metatx_requester_id=user.id if user is not None else None, ) except ValueError as e: logger.error(repr(e)) @@ -354,13 +392,14 @@ async def create_requests( At least one of `contract_id` or `contract_address` must be provided in the request body. """ try: - num_requests = contracts_actions.request_calls( + num_requests = contracts_actions.create_request_calls( db_session=db_session, metatx_requester_id=request.state.user.id, registered_contract_id=data.contract_id, contract_address=data.contract_address, call_specs=data.specifications, ttl_days=data.ttl_days, + live_at=data.live_at, ) except contracts_actions.InvalidAddressFormat as err: raise EngineHTTPException( From a92aeed916d3d3d22b5f7230ed282e370a1aa70c Mon Sep 17 00:00:00 2001 From: kompotkot Date: Wed, 4 Oct 2023 10:50:09 +0000 Subject: [PATCH 3/7] List call_requests check auth with dependency --- engineapi/engineapi/contracts_actions.py | 2 +- engineapi/engineapi/middleware.py | 127 +++++++++++++++++++---- engineapi/engineapi/routes/metatx.py | 44 ++------ 3 files changed, 119 insertions(+), 54 deletions(-) diff --git a/engineapi/engineapi/contracts_actions.py b/engineapi/engineapi/contracts_actions.py index 64c067e4..6cab6912 100644 --- a/engineapi/engineapi/contracts_actions.py +++ b/engineapi/engineapi/contracts_actions.py @@ -430,7 +430,7 @@ def create_request_calls( return len(call_specs) -def get_call_requests( +def get_call_request( db_session: Session, request_id: uuid.UUID, ) -> Tuple[CallRequest, RegisteredContract]: diff --git a/engineapi/engineapi/middleware.py b/engineapi/engineapi/middleware.py index 1b533c4b..64ae5e76 100644 --- a/engineapi/engineapi/middleware.py +++ b/engineapi/engineapi/middleware.py @@ -6,8 +6,9 @@ from uuid import UUID from bugout.data import BugoutResource, BugoutResources, BugoutUser from bugout.exceptions import BugoutResponseException -from fastapi import HTTPException, Request, Response +from fastapi import Header, HTTPException, Request, Response from pydantic import AnyHttpUrl, parse_obj_as +from starlette.datastructures import Headers from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.cors import CORSMiddleware from starlette.responses import Response @@ -34,6 +35,89 @@ from .settings import bugout_client as bc logger = logging.getLogger(__name__) +class InvalidAuthHeaderFormat(Exception): + """ + Raised when authorization header not pass validation. + """ + + +class BugoutUnverifiedAuth(Exception): + """ + Raised when attempted access by unverified Brood account. + """ + + +class BugoutAuthWrongApp(Exception): + """ + Raised when user does not belong to this application. + """ + + +def parse_auth_header(auth_header: str) -> Tuple[str, str]: + """ + Returns: auth_format and user_token passed in authorization header. + """ + auth_list = auth_header.split() + if len(auth_list) != 2: + raise InvalidAuthHeaderFormat("Wrong authorization header") + + return auth_list[0], auth_list[1] + + +def bugout_auth(token: str) -> BugoutUser: + """ + Extended bugout.get_user with additional checks. + """ + user: BugoutUser = bc.get_user(token) + if not user.verified: + raise BugoutUnverifiedAuth("Only verified accounts can have access") + if str(user.application_id) != str(MOONSTREAM_APPLICATION_ID): + raise BugoutAuthWrongApp("User does not belong to this application") + + return user + + +def user_for_auth_header( + authorization: str = Header(None), +) -> Optional[BugoutUser]: + """ + Fetch Bugout user if authorization token provided. + """ + user: Optional[BugoutUser] = None + if authorization is not None: + user_token: str = "" + try: + _, user_token = parse_auth_header(auth_header=authorization) + except InvalidAuthHeaderFormat: + raise EngineHTTPException( + status_code=403, detail="Wrong authorization header" + ) + except Exception as e: + logger.error(f"Error processing Brood response: {str(e)}") + raise EngineHTTPException(status_code=500, detail="Internal server error") + + if user_token != "": + try: + user: BugoutUser = bugout_auth(token=user_token) + except BugoutUnverifiedAuth: + logger.info(f"Attempted access by unverified Brood account: {user.id}") + raise EngineHTTPException( + status_code=403, + detail="Only verified accounts can have access", + ) + except BugoutAuthWrongApp: + raise EngineHTTPException( + status_code=403, detail="User does not belong to this application" + ) + except BugoutResponseException as e: + raise HTTPException(status_code=e.status_code, detail=e.detail) + except Exception as e: + logger.error(f"Error processing Brood response: {str(e)}") + raise HTTPException(status_code=500, detail="Internal server error") + + return user + + class BroodAuthMiddleware(BaseHTTPMiddleware): """ Checks the authorization header on the request. If it represents a verified Brood user, @@ -59,28 +143,33 @@ class BroodAuthMiddleware(BaseHTTPMiddleware): if path in self.whitelist.keys() and self.whitelist[path] == method: return await call_next(request) - authorization_header = request.headers.get("authorization") - if authorization_header is None: + authorization = request.headers.get("authorization") + if authorization is None: return Response( - status_code=403, content="No authorization header passed with request" + status_code=403, + content="No authorization header passed with request", ) - user_token_list = authorization_header.split() - if len(user_token_list) != 2: - return Response(status_code=403, content="Wrong authorization header") - user_token: str = user_token_list[-1] try: - user: BugoutUser = bc.get_user(user_token) - if not user.verified: - logger.info(f"Attempted access by unverified Brood account: {user.id}") - return Response( - status_code=403, - content="Only verified accounts can have access", - ) - if str(user.application_id) != str(MOONSTREAM_APPLICATION_ID): - return Response( - status_code=403, content="User does not belong to this application" - ) + _, user_token = parse_auth_header(auth_header=authorization) + except InvalidAuthHeaderFormat: + return Response(status_code=403, content="Wrong authorization header") + except Exception as e: + logger.error(f"Error processing Brood response: {str(e)}") + return Response(status_code=500, content="Internal server error") + + try: + user: BugoutUser = bugout_auth(token=user_token) + except BugoutUnverifiedAuth: + logger.info(f"Attempted access by unverified Brood account: {user.id}") + return Response( + status_code=403, + content="Only verified accounts can have access", + ) + except BugoutAuthWrongApp: + return Response( + status_code=403, content="User does not belong to this application" + ) except BugoutResponseException as e: return Response(status_code=e.status_code, content=e.detail) except Exception as e: diff --git a/engineapi/engineapi/routes/metatx.py b/engineapi/engineapi/routes/metatx.py index b84129e6..4ac03a4d 100644 --- a/engineapi/engineapi/routes/metatx.py +++ b/engineapi/engineapi/routes/metatx.py @@ -9,8 +9,7 @@ import logging from typing import Dict, List, Optional from uuid import UUID -from bugout.data import BugoutResource, BugoutResources, BugoutUser -from bugout.exceptions import BugoutResponseException +from bugout.data import BugoutUser from fastapi import Body, Depends, FastAPI, Path, Query, Request from sqlalchemy.exc import NoResultFound from sqlalchemy.orm import Session @@ -20,9 +19,9 @@ from ..middleware import ( BroodAuthMiddleware, BugoutCORSMiddleware, EngineHTTPException, + user_for_auth_header, ) -from ..settings import DOCS_TARGET_PATH, MOONSTREAM_APPLICATION_ID -from ..settings import bugout_client as bc +from ..settings import DOCS_TARGET_PATH from ..version import VERSION logger = logging.getLogger(__name__) @@ -285,9 +284,12 @@ async def call_request_types_route( return call_request_types -@app.get("/requests", tags=["requests"], response_model=List[data.CallRequestResponse]) +@app.get( + "/requests", + tags=["requests"], + response_model=List[data.CallRequestResponse], +) async def list_requests_route( - request: Request, contract_id: Optional[UUID] = Query(None), contract_address: Optional[str] = Query(None), caller: str = Query(...), @@ -295,6 +297,7 @@ async def list_requests_route( offset: Optional[int] = Query(None), show_expired: bool = Query(False), show_before_live_at: bool = Query(False), + user: Optional[BugoutUser] = Depends(user_for_auth_header), db_session: Session = Depends(db.yield_db_read_only_session), ) -> List[data.CallRequestResponse]: """ @@ -302,33 +305,6 @@ async def list_requests_route( At least one of `contract_id` or `contract_address` must be provided as query parameters. """ - authorization_header = request.headers.get("authorization") - user: Optional[BugoutUser] = None - if authorization_header is not None: - try: - auth_list = authorization_header.split() - if len(auth_list) != 2: - return EngineHTTPException( - status_code=403, content="Wrong authorization header" - ) - - user = bc.get_user(auth_list[-1]) - if not user.verified: - logger.info(f"Attempted access by unverified Brood account: {user.id}") - return EngineHTTPException( - status_code=403, - content="Only verified accounts can have access", - ) - if str(user.application_id) != str(MOONSTREAM_APPLICATION_ID): - return EngineHTTPException( - status_code=403, content="User does not belong to this application" - ) - except BugoutResponseException as e: - return EngineHTTPException(status_code=e.status_code, content=e.detail) - except Exception as e: - logger.error(f"Error processing Brood response: {str(e)}") - return EngineHTTPException(status_code=500, content="Internal server error") - try: requests = contracts_actions.list_call_requests( db_session=db_session, @@ -364,7 +340,7 @@ async def get_request( At least one of `contract_id` or `contract_address` must be provided as query parameters. """ try: - request = contracts_actions.get_call_requests( + request = contracts_actions.get_call_request( db_session=db_session, request_id=request_id, ) From 47b76ec26da6c7b467c19fdd0af3d8212c090e8b Mon Sep 17 00:00:00 2001 From: kompotkot Date: Wed, 4 Oct 2023 10:55:27 +0000 Subject: [PATCH 4/7] Updated version --- engineapi/engineapi/version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engineapi/engineapi/version.txt b/engineapi/engineapi/version.txt index 5a5831ab..d169b2f2 100644 --- a/engineapi/engineapi/version.txt +++ b/engineapi/engineapi/version.txt @@ -1 +1 @@ -0.0.7 +0.0.8 From d3effd952cd0795883e5f5799dca2fff9074ac64 Mon Sep 17 00:00:00 2001 From: kompotkot Date: Wed, 4 Oct 2023 11:30:58 +0000 Subject: [PATCH 5/7] Depends auth header check should be async --- engineapi/engineapi/middleware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engineapi/engineapi/middleware.py b/engineapi/engineapi/middleware.py index 64ae5e76..5986b417 100644 --- a/engineapi/engineapi/middleware.py +++ b/engineapi/engineapi/middleware.py @@ -77,7 +77,7 @@ def bugout_auth(token: str) -> BugoutUser: return user -def user_for_auth_header( +async def user_for_auth_header( authorization: str = Header(None), ) -> Optional[BugoutUser]: """ From 6b749b5fef93af14a6e23e9aa3deea0877b89a85 Mon Sep 17 00:00:00 2001 From: kompotkot Date: Thu, 7 Dec 2023 09:57:17 +0000 Subject: [PATCH 6/7] Field live_at is nulluble --- ...metatx.py => 6d07739cb13e_live_at_for_metatx.py} | 12 ++++++------ engineapi/engineapi/contracts_actions.py | 13 ++++++++----- engineapi/engineapi/data.py | 2 +- engineapi/engineapi/models.py | 2 +- 4 files changed, 16 insertions(+), 13 deletions(-) rename engineapi/alembic/versions/{4f05d212ea49_live_at_for_metatx.py => 6d07739cb13e_live_at_for_metatx.py} (67%) diff --git a/engineapi/alembic/versions/4f05d212ea49_live_at_for_metatx.py b/engineapi/alembic/versions/6d07739cb13e_live_at_for_metatx.py similarity index 67% rename from engineapi/alembic/versions/4f05d212ea49_live_at_for_metatx.py rename to engineapi/alembic/versions/6d07739cb13e_live_at_for_metatx.py index 04224d0d..5db963d4 100644 --- a/engineapi/alembic/versions/4f05d212ea49_live_at_for_metatx.py +++ b/engineapi/alembic/versions/6d07739cb13e_live_at_for_metatx.py @@ -1,8 +1,8 @@ """Live at for metatx -Revision ID: 4f05d212ea49 -Revises: 040f2dfde5a5 -Create Date: 2023-10-03 10:00:09.730620 +Revision ID: 6d07739cb13e +Revises: cc80e886e153 +Create Date: 2023-12-06 14:33:04.814144 """ from alembic import op @@ -10,15 +10,15 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = '4f05d212ea49' -down_revision = '040f2dfde5a5' +revision = '6d07739cb13e' +down_revision = 'cc80e886e153' branch_labels = None depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.add_column('call_requests', sa.Column('live_at', sa.DateTime(timezone=True), server_default=sa.text("TIMEZONE('utc', statement_timestamp())"), nullable=False)) + op.add_column('call_requests', sa.Column('live_at', sa.DateTime(timezone=True), nullable=True)) # ### end Alembic commands ### diff --git a/engineapi/engineapi/contracts_actions.py b/engineapi/engineapi/contracts_actions.py index 6cab6912..e1c39e29 100644 --- a/engineapi/engineapi/contracts_actions.py +++ b/engineapi/engineapi/contracts_actions.py @@ -5,7 +5,7 @@ import uuid from datetime import datetime, timedelta from typing import Any, Dict, List, Optional, Tuple -from sqlalchemy import func, text +from sqlalchemy import func, or_, text from sqlalchemy.dialects.postgresql import insert from sqlalchemy.engine import Row from sqlalchemy.exc import IntegrityError, NoResultFound @@ -413,7 +413,7 @@ def create_request_calls( request_id=specification.request_id, parameters=specification.parameters, expires_at=expires_at, - live_at=datetime.fromtimestamp(live_at), + live_at=datetime.fromtimestamp(live_at) if live_at is not None else None, ) db_session.add(request) @@ -484,7 +484,10 @@ def list_call_requests( metatx_requester_id: Optional[uuid.UUID] = None, ) -> List[Row[Tuple[CallRequest, RegisteredContract, CallRequestType]]]: """ - List call requests for the given moonstream_user_id + List call requests. + + Argument moonstream_user_id took from authorization workflow. And if it is specified + then user has access to call_requests before live_at param. """ if caller is None: raise ValueError("caller must be specified") @@ -525,11 +528,11 @@ def list_call_requests( ) if not show_before_live_at: query = query.filter( - CallRequest.live_at < func.now(), + or_(CallRequest.live_at < func.now(), CallRequest.live_at == None) ) else: query = query.filter( - CallRequest.live_at < func.now(), + or_(CallRequest.live_at < func.now(), CallRequest.live_at == None) ) if offset is not None: diff --git a/engineapi/engineapi/data.py b/engineapi/engineapi/data.py index ed4941cb..aa15795d 100644 --- a/engineapi/engineapi/data.py +++ b/engineapi/engineapi/data.py @@ -307,7 +307,7 @@ class CallRequestResponse(BaseModel): request_id: str parameters: Dict[str, Any] expires_at: Optional[datetime] = None - live_at: datetime + live_at: Optional[datetime] = None created_at: datetime updated_at: datetime diff --git a/engineapi/engineapi/models.py b/engineapi/engineapi/models.py index cc5b68ca..f1532a3d 100644 --- a/engineapi/engineapi/models.py +++ b/engineapi/engineapi/models.py @@ -316,7 +316,7 @@ class CallRequest(Base): parameters = Column(JSONB, nullable=False) expires_at = Column(DateTime(timezone=True), nullable=True, index=True) - live_at = Column(DateTime(timezone=True), server_default=utcnow(), nullable=False) + live_at = Column(DateTime(timezone=True), nullable=True) created_at = Column( DateTime(timezone=True), server_default=utcnow(), nullable=False From 0d36368dd2521ec6c9efb3b2516cc4408feb7bcd Mon Sep 17 00:00:00 2001 From: kompotkot Date: Thu, 1 Feb 2024 10:15:47 +0000 Subject: [PATCH 7/7] Updated revision version number --- engineapi/alembic/versions/6d07739cb13e_live_at_for_metatx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engineapi/alembic/versions/6d07739cb13e_live_at_for_metatx.py b/engineapi/alembic/versions/6d07739cb13e_live_at_for_metatx.py index 5db963d4..498705a4 100644 --- a/engineapi/alembic/versions/6d07739cb13e_live_at_for_metatx.py +++ b/engineapi/alembic/versions/6d07739cb13e_live_at_for_metatx.py @@ -1,7 +1,7 @@ """Live at for metatx Revision ID: 6d07739cb13e -Revises: cc80e886e153 +Revises: 71e888082a6d Create Date: 2023-12-06 14:33:04.814144 """ @@ -11,7 +11,7 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. revision = '6d07739cb13e' -down_revision = 'cc80e886e153' +down_revision = '71e888082a6d' branch_labels = None depends_on = None