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

Wyświetl plik

@ -238,7 +238,6 @@ class BlockchainsResponse(BaseModel):
class RegisterContractRequest(BaseModel):
blockchain: str
address: str
contract_type: ContractType
title: Optional[str] = None
description: Optional[str] = None
image_uri: Optional[str] = None
@ -251,29 +250,6 @@ class UpdateContractRequest(BaseModel):
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):
id: UUID
blockchain: Optional[str] = None

Wyświetl plik

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