From de2afed55953a5ae3bf128f8fa995df21c635754 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 20 Nov 2023 15:34:47 +0200 Subject: [PATCH] Fix if in get latest version. Fix join. Add version parameter to all public endpoints. --- engineapi/engineapi/actions.py | 84 ++++++++++++++++++----- engineapi/engineapi/data.py | 2 +- engineapi/engineapi/routes/leaderboard.py | 55 ++++++++++----- 3 files changed, 105 insertions(+), 36 deletions(-) diff --git a/engineapi/engineapi/actions.py b/engineapi/engineapi/actions.py index c700b172..ef7f79b6 100644 --- a/engineapi/engineapi/actions.py +++ b/engineapi/engineapi/actions.py @@ -11,7 +11,7 @@ from hexbytes import HexBytes import requests # type: ignore from sqlalchemy.dialects.postgresql import insert from sqlalchemy.orm import Session -from sqlalchemy import func, text, or_, Subquery +from sqlalchemy import func, text, or_, and_, Subquery from sqlalchemy.engine import Row from web3 import Web3 from web3.types import ChecksumAddress @@ -970,7 +970,7 @@ def leaderboard_version_filter( version_number: Optional[int] = None, ) -> Union[Subquery, int]: # Subquery to get the latest version number for the given leaderboard - if not version_number: + if version_number is None: latest_version = ( db_session.query(func.max(LeaderboardVersion.version_number)).filter( LeaderboardVersion.leaderboard_id == leaderboard_id, @@ -983,16 +983,38 @@ def leaderboard_version_filter( return latest_version -def get_leaderboard_total_count(db_session: Session, leaderboard_id) -> int: +def get_leaderboard_total_count( + db_session: Session, leaderboard_id, version_number: Optional[int] = None +) -> int: """ - Get the total number of claimants in the leaderboard + Get the total number of position in the leaderboard """ - return ( - db_session.query(LeaderboardScores) - .filter(LeaderboardScores.leaderboard_id == leaderboard_id) - .count() + + latest_version = leaderboard_version_filter( + db_session=db_session, + leaderboard_id=leaderboard_id, + version_number=version_number, ) + total_count = ( + db_session.query(func.count(LeaderboardScores.id)) + .join( + LeaderboardVersion, + and_( + LeaderboardVersion.leaderboard_id == LeaderboardScores.leaderboard_id, + LeaderboardVersion.version_number + == LeaderboardScores.leaderboard_version_number, + ), + ) + .filter( + LeaderboardVersion.published == True, + LeaderboardVersion.version_number == latest_version, + ) + .filter(LeaderboardScores.leaderboard_id == leaderboard_id) + ).scalar() + + return total_count + def get_leaderboard_info( db_session: Session, leaderboard_id: uuid.UUID, version_number: Optional[int] = None @@ -1006,6 +1028,7 @@ def get_leaderboard_info( leaderboard_id=leaderboard_id, version_number=version_number, ) + leaderboard = ( db_session.query( Leaderboard.id, @@ -1021,7 +1044,11 @@ def get_leaderboard_info( ) .join( LeaderboardVersion, - LeaderboardVersion.leaderboard_id == Leaderboard.id, + and_( + LeaderboardVersion.leaderboard_id == LeaderboardScores.leaderboard_id, + LeaderboardVersion.version_number + == LeaderboardScores.leaderboard_version_number, + ), isouter=True, ) .filter( @@ -1030,8 +1057,7 @@ def get_leaderboard_info( ) .filter(Leaderboard.id == leaderboard_id) .group_by(Leaderboard.id, Leaderboard.title, Leaderboard.description) - .one() - ) + ).one() return leaderboard @@ -1146,7 +1172,11 @@ def get_position( ) .join( LeaderboardVersion, - LeaderboardVersion.leaderboard_id == LeaderboardScores.leaderboard_id, + and_( + LeaderboardVersion.leaderboard_id == LeaderboardScores.leaderboard_id, + LeaderboardVersion.version_number + == LeaderboardScores.leaderboard_version_number, + ), ) .filter( LeaderboardVersion.published == True, @@ -1222,7 +1252,11 @@ def get_leaderboard_positions( ) .join( LeaderboardVersion, - LeaderboardVersion.leaderboard_id == LeaderboardScores.leaderboard_id, + and_( + LeaderboardVersion.leaderboard_id == LeaderboardScores.leaderboard_id, + LeaderboardVersion.version_number + == LeaderboardScores.leaderboard_version_number, + ), ) .filter(LeaderboardScores.leaderboard_id == leaderboard_id) .filter(LeaderboardVersion.published == True) @@ -1260,7 +1294,11 @@ def get_qurtiles( ) .join( LeaderboardVersion, - LeaderboardVersion.leaderboard_id == LeaderboardScores.leaderboard_id, + and_( + LeaderboardVersion.leaderboard_id == LeaderboardScores.leaderboard_id, + LeaderboardVersion.version_number + == LeaderboardScores.leaderboard_version_number, + ), ) .filter( LeaderboardVersion.published == True, @@ -1314,7 +1352,11 @@ def get_ranks( ) .join( LeaderboardVersion, - LeaderboardVersion.leaderboard_id == LeaderboardScores.leaderboard_id, + and_( + LeaderboardVersion.leaderboard_id == LeaderboardScores.leaderboard_id, + LeaderboardVersion.version_number + == LeaderboardScores.leaderboard_version_number, + ), ) .filter( LeaderboardVersion.published == True, @@ -1361,7 +1403,11 @@ def get_rank( ) .join( LeaderboardVersion, - LeaderboardVersion.leaderboard_id == LeaderboardScores.leaderboard_id, + and_( + LeaderboardVersion.leaderboard_id == LeaderboardScores.leaderboard_id, + LeaderboardVersion.version_number + == LeaderboardScores.leaderboard_version_number, + ), ) .filter( LeaderboardVersion.published == True, @@ -1546,7 +1592,11 @@ def add_scores( insert_statement = insert(LeaderboardScores).values(leaderboard_scores) result_stmt = insert_statement.on_conflict_do_update( - index_elements=[LeaderboardScores.address, LeaderboardScores.leaderboard_id], + index_elements=[ + LeaderboardScores.address, + LeaderboardScores.leaderboard_id, + LeaderboardScores.leaderboard_version_number, + ], set_=dict( score=insert_statement.excluded.score, points_data=insert_statement.excluded.points_data, diff --git a/engineapi/engineapi/data.py b/engineapi/engineapi/data.py index 6b7e3f82..395d913d 100644 --- a/engineapi/engineapi/data.py +++ b/engineapi/engineapi/data.py @@ -452,5 +452,5 @@ class LeaderboardVersion(BaseModel): updated_at: datetime -class LeaderboardVersionUpdateRequest(BaseModel): +class LeaderboardVersionRequest(BaseModel): publish: bool diff --git a/engineapi/engineapi/routes/leaderboard.py b/engineapi/engineapi/routes/leaderboard.py index 01c93d7e..20c6e0dd 100644 --- a/engineapi/engineapi/routes/leaderboard.py +++ b/engineapi/engineapi/routes/leaderboard.py @@ -355,6 +355,7 @@ async def get_leaderboards( ) async def count_addresses( leaderboard_id: UUID = Query(..., description="Leaderboard ID"), + version: Optional[int] = Query(None, description="Version of the leaderboard."), db_session: Session = Depends(db.yield_db_session), ) -> data.CountAddressesResponse: """ @@ -373,7 +374,7 @@ async def count_addresses( logger.error(f"Error while getting leaderboard: {e}") raise EngineHTTPException(status_code=500, detail="Internal server error") - count = actions.get_leaderboard_total_count(db_session, leaderboard_id) + count = actions.get_leaderboard_total_count(db_session, leaderboard_id, version) return data.CountAddressesResponse(count=count) @@ -384,12 +385,13 @@ async def count_addresses( async def leadeboard_info( leaderboard_id: UUID = Query(..., description="Leaderboard ID"), db_session: Session = Depends(db.yield_db_session), + version: Optional[int] = Query(None, description="Version of the leaderboard."), ) -> data.LeaderboardInfoResponse: """ Returns leaderboard info. """ try: - leaderboard = actions.get_leaderboard_info(db_session, leaderboard_id) + leaderboard = actions.get_leaderboard_info(db_session, leaderboard_id, version) except NoResultFound as e: raise EngineHTTPException( status_code=404, @@ -443,6 +445,7 @@ async def get_scores_changes( async def quartiles( leaderboard_id: UUID = Query(..., description="Leaderboard ID"), db_session: Session = Depends(db.yield_db_session), + version: Optional[int] = Query(None, description="Version of the leaderboard."), ) -> data.QuartilesResponse: """ Returns the quartiles of the leaderboard. @@ -460,7 +463,7 @@ async def quartiles( raise EngineHTTPException(status_code=500, detail="Internal server error") try: - q1, q2, q3 = actions.get_qurtiles(db_session, leaderboard_id) + q1, q2, q3 = actions.get_qurtiles(db_session, leaderboard_id, version) except actions.LeaderboardIsEmpty: raise EngineHTTPException(status_code=204, detail="Leaderboard is empty.") @@ -489,6 +492,7 @@ async def position( normalize_addresses: bool = Query( True, description="Normalize addresses to checksum." ), + version: Optional[int] = Query(None, description="Version of the leaderboard."), db_session: Session = Depends(db.yield_db_session), ) -> List[data.LeaderboardPosition]: """ @@ -512,7 +516,13 @@ async def position( address = Web3.toChecksumAddress(address) positions = actions.get_position( - db_session, leaderboard_id, address, window_size, limit, offset + db_session, + leaderboard_id, + address, + window_size, + limit, + offset, + version, ) results = [ @@ -536,6 +546,7 @@ async def rank( rank: int = Query(1, description="Rank to get."), limit: Optional[int] = Query(None), offset: Optional[int] = Query(None), + version: Optional[int] = Query(None, description="Version of the leaderboard."), db_session: Session = Depends(db.yield_db_session), ) -> List[data.LeaderboardPosition]: """ @@ -555,7 +566,12 @@ async def rank( raise EngineHTTPException(status_code=500, detail="Internal server error") leaderboard_rank = actions.get_rank( - db_session, leaderboard_id, rank, limit=limit, offset=offset + db_session, + leaderboard_id, + rank, + limit=limit, + offset=offset, + version_number=version, ) results = [ data.LeaderboardPosition( @@ -572,6 +588,7 @@ async def rank( @app.get("/ranks", response_model=List[data.RanksResponse], tags=["Public Endpoints"]) async def ranks( leaderboard_id: UUID = Query(..., description="Leaderboard ID"), + version: Optional[int] = Query(None, description="Version of the leaderboard."), db_session: Session = Depends(db.yield_db_session), ) -> List[data.RanksResponse]: """ @@ -590,7 +607,7 @@ async def ranks( logger.error(f"Error while getting leaderboard: {e}") raise EngineHTTPException(status_code=500, detail="Internal server error") - ranks = actions.get_ranks(db_session, leaderboard_id) + ranks = actions.get_ranks(db_session, leaderboard_id, version) results = [ data.RanksResponse( score=rank.score, @@ -1038,8 +1055,11 @@ async def leaderboard_version_handler( async def create_leaderboard_version( request: Request, leaderboard_id: UUID = Path(..., description="Leaderboard ID"), - version: int = Query(..., description="Version of the leaderboard."), db_session: Session = Depends(db.yield_db_session), + request_body: data.LeaderboardVersionRequest = Body( + ..., + description="JSON object specifying whether to publish or unpublish version.", + ), Authorization: str = AuthHeader, ) -> data.LeaderboardVersion: """ @@ -1064,23 +1084,22 @@ async def create_leaderboard_version( ) try: - leaderboard_version = actions.create_leaderboard_version( + new_version = actions.create_leaderboard_version( db_session=db_session, leaderboard_id=leaderboard_id, - version=version, - ) - except BugoutResponseException as e: - raise EngineHTTPException(status_code=e.status_code, detail=e.detail) - except actions.LeaderboardConfigNotFound as e: - raise EngineHTTPException( - status_code=404, - detail="Leaderboard config not found.", + publish=request_body.publish, ) except Exception as e: logger.error(f"Error while creating leaderboard version: {e}") raise EngineHTTPException(status_code=500, detail="Internal server error") - return leaderboard_version + return data.LeaderboardVersion( + leaderboard_id=new_version.leaderboard_id, + version=new_version.version_number, + published=new_version.published, + created_at=new_version.created_at, + updated_at=new_version.updated_at, + ) @app.put( @@ -1092,7 +1111,7 @@ async def update_leaderboard_version_handler( request: Request, leaderboard_id: UUID = Path(..., description="Leaderboard ID"), version: int = Path(..., description="Version of the leaderboard."), - request_body: data.LeaderboardVersionUpdateRequest = Body( + request_body: data.LeaderboardVersionRequest = Body( ..., description="JSON object specifying whether to publish or unpublish version.", ),