Fixed metatx contract routes to work with new db model

pull/882/head
kompotkot 2023-08-03 12:36:13 +00:00
rodzic 2d93c307ab
commit 1589c8c65d
3 zmienionych plików z 157 dodań i 96 usunięć

Wyświetl plik

@ -6,13 +6,21 @@ from datetime import timedelta
from typing import Any, Dict, List, Optional, Tuple from typing import Any, Dict, List, Optional, Tuple
from sqlalchemy import func, text from sqlalchemy import func, text
from sqlalchemy.dialects.postgresql import insert
from sqlalchemy.engine import Row
from sqlalchemy.exc import IntegrityError, NoResultFound from sqlalchemy.exc import IntegrityError, NoResultFound
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from web3 import Web3 from web3 import Web3
from . import data, db from . import data, db
from .data import ContractType from .data import ContractType
from .models import Blockchain, CallRequest, CallRequestType, RegisteredContract from .models import (
Blockchain,
CallRequest,
CallRequestType,
MetatxHolder,
RegisteredContract,
)
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -30,6 +38,12 @@ class InvalidAddressFormat(Exception):
""" """
class UnsupportedBlockchain(Exception):
"""
Raised when unsupported blockchain specified.
"""
class ContractAlreadyRegistered(Exception): class ContractAlreadyRegistered(Exception):
pass pass
@ -72,23 +86,33 @@ def validate_method_and_params(
def register_contract( def register_contract(
db_session: Session, db_session: Session,
blockchain: str, blockchain_name: str,
address: str, address: str,
contract_type: ContractType, metatx_holder_id: uuid.UUID,
moonstream_user_id: uuid.UUID,
title: Optional[str], title: Optional[str],
description: Optional[str], description: Optional[str],
image_uri: Optional[str], image_uri: Optional[str],
) -> data.RegisteredContract: ) -> Tuple[RegisteredContract, Blockchain]:
""" """
Register a contract against the Engine instance Register a contract against the Engine instance
""" """
try: try:
blockchain = (
db_session.query(Blockchain)
.filter(Blockchain.name == blockchain_name)
.one_or_none()
)
if blockchain is None:
raise UnsupportedBlockchain("Unsupported blockchain specified")
metatx_holder_stmt = insert(MetatxHolder.__table__).values(id=metatx_holder_id)
metatx_holder_stmt_do_nothing_stmt = metatx_holder_stmt.on_conflict_do_nothing()
db_session.execute(metatx_holder_stmt_do_nothing_stmt)
contract = RegisteredContract( contract = RegisteredContract(
blockchain=blockchain, blockchain_id=blockchain.id,
address=Web3.toChecksumAddress(address), address=Web3.toChecksumAddress(address),
contract_type=contract_type.value, metatx_holder_id=metatx_holder_id,
moonstream_user_id=moonstream_user_id,
title=title, title=title,
description=description, description=description,
image_uri=image_uri, image_uri=image_uri,
@ -103,38 +127,42 @@ def register_contract(
logger.error(repr(err)) logger.error(repr(err))
raise raise
return contract return (contract, blockchain)
def update_registered_contract( def update_registered_contract(
db_session: Session, db_session: Session,
moonstream_user_id: uuid.UUID, metatx_holder_id: uuid.UUID,
contract_id: uuid.UUID, contract_id: uuid.UUID,
title: Optional[str] = None, title: Optional[str] = None,
description: Optional[str] = None, description: Optional[str] = None,
image_uri: Optional[str] = None, image_uri: Optional[str] = None,
ignore_nulls: bool = True, ignore_nulls: bool = True,
) -> data.RegisteredContract: ) -> Tuple[RegisteredContract, Blockchain]:
""" """
Update the registered contract with the given contract ID provided that the user with moonstream_user_id Update the registered contract with the given contract ID provided that the user with metatx_holder_id
has access to it. has access to it.
""" """
query = db_session.query(RegisteredContract).filter( contract_with_blockchain = (
RegisteredContract.id == contract_id, db_session.query(RegisteredContract, Blockchain)
RegisteredContract.moonstream_user_id == moonstream_user_id, .join(Blockchain, Blockchain.id == RegisteredContract.blockchain_id)
.filter(
RegisteredContract.id == contract_id,
RegisteredContract.metatx_holder_id == metatx_holder_id,
)
.one()
) )
registered_contract, blockchain = contract_with_blockchain
contract = query.one()
if not (title is None and ignore_nulls): if not (title is None and ignore_nulls):
contract.title = title registered_contract.title = title
if not (description is None and ignore_nulls): if not (description is None and ignore_nulls):
contract.description = description registered_contract.description = description
if not (image_uri is None and ignore_nulls): if not (image_uri is None and ignore_nulls):
contract.image_uri = image_uri registered_contract.image_uri = image_uri
try: try:
db_session.add(contract) db_session.add(registered_contract)
db_session.commit() db_session.commit()
except Exception as err: except Exception as err:
logger.error( logger.error(
@ -143,24 +171,27 @@ def update_registered_contract(
db_session.rollback() db_session.rollback()
raise raise
return contract return (registered_contract, blockchain)
def get_registered_contract( def get_registered_contract(
db_session: Session, db_session: Session,
moonstream_user_id: uuid.UUID, metatx_holder_id: uuid.UUID,
contract_id: uuid.UUID, contract_id: uuid.UUID,
) -> RegisteredContract: ) -> Tuple[RegisteredContract, Blockchain]:
""" """
Get registered contract by ID. Get registered contract by ID.
""" """
contract = ( contract_with_blockchain = (
db_session.query(RegisteredContract) db_session.query(RegisteredContract, Blockchain)
.filter(RegisteredContract.moonstream_user_id == moonstream_user_id) .join(Blockchain, Blockchain.id == RegisteredContract.blockchain_id)
.filter(RegisteredContract.metatx_holder_id == metatx_holder_id)
.filter(RegisteredContract.id == contract_id) .filter(RegisteredContract.id == contract_id)
.one() .one()
) )
return contract registered_contract, blockchain = contract_with_blockchain
return (registered_contract, blockchain)
def lookup_registered_contracts( def lookup_registered_contracts(
@ -170,7 +201,7 @@ def lookup_registered_contracts(
address: Optional[str] = None, address: Optional[str] = None,
limit: int = 10, limit: int = 10,
offset: Optional[int] = None, offset: Optional[int] = None,
) -> List[Tuple[RegisteredContract, Blockchain]]: ) -> List[Row[Tuple[RegisteredContract, Blockchain]]]:
""" """
Lookup a registered contract Lookup a registered contract
""" """
@ -191,35 +222,39 @@ def lookup_registered_contracts(
if offset is not None: if offset is not None:
query = query.offset(offset) query = query.offset(offset)
registered_contracts_with_blockchain = query.limit(limit).all() contracts_with_blockchain = query.limit(limit).all()
return registered_contracts_with_blockchain return contracts_with_blockchain
def delete_registered_contract( def delete_registered_contract(
db_session: Session, db_session: Session,
moonstream_user_id: uuid.UUID, metatx_holder_id: uuid.UUID,
registered_contract_id: uuid.UUID, registered_contract_id: uuid.UUID,
) -> RegisteredContract: ) -> Tuple[RegisteredContract, Blockchain]:
""" """
Delete a registered contract Delete a registered contract
""" """
try: try:
registered_contract = ( contract_with_blockchain = (
db_session.query(RegisteredContract) db_session.query(RegisteredContract, Blockchain)
.filter(RegisteredContract.moonstream_user_id == moonstream_user_id) .join(Blockchain, Blockchain.id == RegisteredContract.blockchain_id)
.filter(RegisteredContract.metatx_holder_id == metatx_holder_id)
.filter(RegisteredContract.id == registered_contract_id) .filter(RegisteredContract.id == registered_contract_id)
.one() .one()
) )
contract = contract_with_blockchain[0]
db_session.delete(registered_contract) db_session.delete(contract)
db_session.commit() db_session.commit()
except Exception as err: except Exception as err:
db_session.rollback() db_session.rollback()
logger.error(repr(err)) logger.error(repr(err))
raise raise
return registered_contract registered_contract, blockchain = contract_with_blockchain
return (registered_contract, blockchain)
def request_calls( def request_calls(

Wyświetl plik

@ -238,7 +238,6 @@ class BlockchainsResponse(BaseModel):
class RegisterContractRequest(BaseModel): class RegisterContractRequest(BaseModel):
blockchain: str blockchain: str
address: str address: str
contract_type: ContractType
title: Optional[str] = None title: Optional[str] = None
description: Optional[str] = None description: Optional[str] = None
image_uri: Optional[str] = None image_uri: Optional[str] = None
@ -251,29 +250,6 @@ class UpdateContractRequest(BaseModel):
ignore_nulls: bool = True ignore_nulls: bool = True
class RegisteredContract(BaseModel):
id: UUID
blockchain: str
address: str
metatx_holder_id: UUID
title: Optional[str] = None
description: Optional[str] = None
image_uri: Optional[str] = None
created_at: datetime
updated_at: datetime
@validator("id", "metatx_holder_id")
def validate_uuids(cls, v):
return str(v)
@validator("created_at", "updated_at")
def validate_datetimes(cls, v):
return v.isoformat()
class Config:
orm_mode = True
class RegisteredContractResponse(BaseModel): class RegisteredContractResponse(BaseModel):
id: UUID id: UUID
blockchain: Optional[str] = None blockchain: Optional[str] = None

Wyświetl plik

@ -88,7 +88,7 @@ async def blockchains_route(
tags=["contracts"], tags=["contracts"],
response_model=List[data.RegisteredContractResponse], response_model=List[data.RegisteredContractResponse],
) )
async def list_registered_contracts( async def list_registered_contracts_route(
request: Request, request: Request,
blockchain: Optional[str] = Query(None), blockchain: Optional[str] = Query(None),
address: Optional[str] = Query(None), address: Optional[str] = Query(None),
@ -133,20 +133,20 @@ async def list_registered_contracts(
@app.get( @app.get(
"/contracts/{contract_id}", "/contracts/{contract_id}",
tags=["contracts"], tags=["contracts"],
response_model=data.RegisteredContract, response_model=data.RegisteredContractResponse,
) )
async def get_registered_contract( async def get_registered_contract_route(
request: Request, request: Request,
contract_id: UUID = Path(...), contract_id: UUID = Path(...),
db_session: Session = Depends(db.yield_db_read_only_session), db_session: Session = Depends(db.yield_db_read_only_session),
) -> List[data.RegisteredContract]: ) -> List[data.RegisteredContractResponse]:
""" """
Get the contract by ID. Get the contract by ID.
""" """
try: try:
contract = contracts_actions.get_registered_contract( contract_with_blockchain = contracts_actions.get_registered_contract(
db_session=db_session, db_session=db_session,
moonstream_user_id=request.state.user.id, metatx_holder_id=request.state.user.id,
contract_id=contract_id, contract_id=contract_id,
) )
except NoResultFound: except NoResultFound:
@ -157,57 +157,87 @@ async def get_registered_contract(
except Exception as err: except Exception as err:
logger.error(repr(err)) logger.error(repr(err))
raise EngineHTTPException(status_code=500) raise EngineHTTPException(status_code=500)
return contract
return data.RegisteredContractResponse(
id=contract_with_blockchain[0].id,
blockchain=contract_with_blockchain[1].name,
address=contract_with_blockchain[0].address,
metatx_holder_id=contract_with_blockchain[0].metatx_holder_id,
title=contract_with_blockchain[0].title,
description=contract_with_blockchain[0].description,
image_uri=contract_with_blockchain[0].image_uri,
created_at=contract_with_blockchain[0].created_at,
updated_at=contract_with_blockchain[0].updated_at,
)
@app.post("/contracts", tags=["contracts"], response_model=data.RegisteredContract) @app.post(
async def register_contract( "/contracts", tags=["contracts"], response_model=data.RegisteredContractResponse
)
async def register_contract_route(
request: Request, request: Request,
contract: data.RegisterContractRequest = Body(...), contract: data.RegisterContractRequest = Body(...),
db_session: Session = Depends(db.yield_db_session), db_session: Session = Depends(db.yield_db_session),
) -> data.RegisteredContract: ) -> data.RegisteredContractResponse:
""" """
Allows users to register contracts. Allows users to register contracts.
""" """
try: try:
registered_contract = contracts_actions.register_contract( contract_with_blockchain = contracts_actions.register_contract(
db_session=db_session, db_session=db_session,
moonstream_user_id=request.state.user.id, metatx_holder_id=request.state.user.id,
blockchain=contract.blockchain, blockchain_name=contract.blockchain,
address=contract.address, address=contract.address,
contract_type=contract.contract_type,
title=contract.title, title=contract.title,
description=contract.description, description=contract.description,
image_uri=contract.image_uri, image_uri=contract.image_uri,
) )
except contracts_actions.UnsupportedBlockchain:
raise EngineHTTPException(
status_code=400, detail="Unsupported blockchain specified"
)
except contracts_actions.ContractAlreadyRegistered: except contracts_actions.ContractAlreadyRegistered:
raise EngineHTTPException( raise EngineHTTPException(
status_code=409, status_code=409,
detail="Contract already registered", detail="Contract already registered",
) )
return registered_contract except Exception as err:
logger.error(repr(err))
raise EngineHTTPException(status_code=500)
return data.RegisteredContractResponse(
id=contract_with_blockchain[0].id,
blockchain=contract_with_blockchain[1].name,
address=contract_with_blockchain[0].address,
metatx_holder_id=contract_with_blockchain[0].metatx_holder_id,
title=contract_with_blockchain[0].title,
description=contract_with_blockchain[0].description,
image_uri=contract_with_blockchain[0].image_uri,
created_at=contract_with_blockchain[0].created_at,
updated_at=contract_with_blockchain[0].updated_at,
)
@app.put( @app.put(
"/contracts/{contract_id}", "/contracts/{contract_id}",
tags=["contracts"], tags=["contracts"],
response_model=data.RegisteredContract, response_model=data.RegisteredContractResponse,
) )
async def update_contract( async def update_contract_route(
request: Request, request: Request,
contract_id: UUID = Path(...), contract_id: UUID = Path(...),
update_info: data.UpdateContractRequest = Body(...), update_info: data.UpdateContractRequest = Body(...),
db_session: Session = Depends(db.yield_db_session), db_session: Session = Depends(db.yield_db_session),
) -> data.RegisteredContract: ) -> data.RegisteredContractResponse:
try: try:
contract = contracts_actions.update_registered_contract( contract_with_blockchain = contracts_actions.update_registered_contract(
db_session, db_session=db_session,
request.state.user.id, metatx_holder_id=request.state.user.id,
contract_id, contract_id=contract_id,
update_info.title, title=update_info.title,
update_info.description, description=update_info.description,
update_info.image_uri, image_uri=update_info.image_uri,
update_info.ignore_nulls, ignore_nulls=update_info.ignore_nulls,
) )
except NoResultFound: except NoResultFound:
raise EngineHTTPException( raise EngineHTTPException(
@ -218,33 +248,53 @@ async def update_contract(
logger.error(repr(err)) logger.error(repr(err))
raise EngineHTTPException(status_code=500) raise EngineHTTPException(status_code=500)
return contract return data.RegisteredContractResponse(
id=contract_with_blockchain[0].id,
blockchain=contract_with_blockchain[1].name,
address=contract_with_blockchain[0].address,
metatx_holder_id=contract_with_blockchain[0].metatx_holder_id,
title=contract_with_blockchain[0].title,
description=contract_with_blockchain[0].description,
image_uri=contract_with_blockchain[0].image_uri,
created_at=contract_with_blockchain[0].created_at,
updated_at=contract_with_blockchain[0].updated_at,
)
@app.delete( @app.delete(
"/contracts/{contract_id}", "/contracts/{contract_id}",
tags=["contracts"], tags=["contracts"],
response_model=data.RegisteredContract, response_model=data.RegisteredContractResponse,
) )
async def delete_contract( async def delete_contract_route(
request: Request, request: Request,
contract_id: UUID, contract_id: UUID = Path(...),
db_session: Session = Depends(db.yield_db_session), db_session: Session = Depends(db.yield_db_session),
) -> data.RegisteredContract: ) -> data.RegisteredContractResponse:
""" """
Allows users to delete contracts that they have registered. Allows users to delete contracts that they have registered.
""" """
try: try:
deleted_contract = contracts_actions.delete_registered_contract( deleted_contract_with_blockchain = contracts_actions.delete_registered_contract(
db_session=db_session, db_session=db_session,
moonstream_user_id=request.state.user.id, metatx_holder_id=request.state.user.id,
registered_contract_id=contract_id, registered_contract_id=contract_id,
) )
except Exception as err: except Exception as err:
logger.error(repr(err)) logger.error(repr(err))
raise EngineHTTPException(status_code=500) raise EngineHTTPException(status_code=500)
return deleted_contract return data.RegisteredContractResponse(
id=deleted_contract_with_blockchain[0].id,
blockchain=deleted_contract_with_blockchain[1].name,
address=deleted_contract_with_blockchain[0].address,
metatx_holder_id=deleted_contract_with_blockchain[0].metatx_holder_id,
title=deleted_contract_with_blockchain[0].title,
description=deleted_contract_with_blockchain[0].description,
image_uri=deleted_contract_with_blockchain[0].image_uri,
created_at=deleted_contract_with_blockchain[0].created_at,
updated_at=deleted_contract_with_blockchain[0].updated_at,
)
# TODO(kompotkot): route `/contracts/types` deprecated # TODO(kompotkot): route `/contracts/types` deprecated