Merge pull request #916 from moonstream-to/leaderboard-config-management

Add initiate leaderboard config managment.
pull/925/head
Andrey Dolgolev 2023-09-04 17:29:25 +03:00 zatwierdzone przez GitHub
commit 6292aecd3a
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
6 zmienionych plików z 382 dodań i 6 usunięć

Wyświetl plik

@ -1,10 +1,11 @@
from datetime import datetime
from collections import Counter
from typing import List, Any, Optional, Dict, Union, Tuple
import json
from typing import List, Any, Optional, Dict, Union, Tuple, cast
import uuid
import logging
from bugout.data import BugoutResource
from bugout.data import BugoutResource, BugoutSearchResult
from eth_typing import Address
from hexbytes import HexBytes
import requests # type: ignore
@ -15,7 +16,7 @@ from sqlalchemy.engine import Row
from web3 import Web3
from web3.types import ChecksumAddress
from .data import Score, LeaderboardScore
from .data import Score, LeaderboardScore, LeaderboardConfigUpdate, LeaderboardConfig
from .contracts import Dropper_interface, ERC20_interface, Terminus_interface
from .models import (
DropperClaimant,
@ -26,11 +27,12 @@ from .models import (
)
from . import signatures
from .settings import (
bugout_client as bc,
BLOCKCHAIN_WEB3_PROVIDERS,
LEADERBOARD_RESOURCE_TYPE,
MOONSTREAM_APPLICATION_ID,
MOONSTREAM_ADMIN_ACCESS_TOKEN,
bugout_client as bc,
MOONSTREAM_LEADERBOARD_CONFIGURATION_JOURNAL_ID,
)
@ -77,6 +79,18 @@ class LeaderboardDeleteError(Exception):
pass
class LeaderboardConfigNotFound(Exception):
pass
class LeaderboardConfigAlreadyActive(Exception):
pass
class LeaderboardConfigAlreadyInactive(Exception):
pass
BATCH_SIGNATURE_PAGE_SIZE = 500
logger = logging.getLogger(__name__)
@ -1491,6 +1505,130 @@ def list_leaderboards_resources(
return query.all()
def get_leaderboard_config_entry(
leaderboard_id: uuid.UUID,
) -> BugoutSearchResult:
query = f"#leaderboard_id:{leaderboard_id}"
configs = bc.search(
token=MOONSTREAM_ADMIN_ACCESS_TOKEN,
journal_id=MOONSTREAM_LEADERBOARD_CONFIGURATION_JOURNAL_ID,
query=query,
limit=1,
)
results = cast(List[BugoutSearchResult], configs.results)
if len(configs.results) == 0 or results[0].content is None:
raise LeaderboardConfigNotFound(
f"Leaderboard config not found for {leaderboard_id}"
)
return results[0]
def get_leaderboard_config(
leaderboard_id: uuid.UUID,
) -> Dict[str, Any]:
"""
Return leaderboard config from leaderboard generator journal
"""
entry = get_leaderboard_config_entry(leaderboard_id)
content = json.loads(entry.content) # type: ignore
if "status:active" not in entry.tags:
content["leaderboard_auto_update_active"] = False
else:
content["leaderboard_auto_update_active"] = True
return content
def update_leaderboard_config(
leaderboard_id: uuid.UUID, config: LeaderboardConfigUpdate
) -> Dict[str, Any]:
"""
Update leaderboard config in leaderboard generator journal
"""
entry_config = get_leaderboard_config_entry(leaderboard_id)
current_config = LeaderboardConfig(**json.loads(entry_config.content)) # type: ignore
new_params = config.params
for key, value in new_params.items():
if key not in current_config.params:
continue
current_config.params[key] = value
# we replace values of parameters that are not None
entry = bc.update_entry_content(
token=MOONSTREAM_ADMIN_ACCESS_TOKEN,
journal_id=MOONSTREAM_LEADERBOARD_CONFIGURATION_JOURNAL_ID,
title=entry_config.title,
entry_id=entry_config.entry_url.split("/")[-1],
content=json.dumps(current_config.dict()),
)
new_config = json.loads(entry.content)
if "status:active" not in entry.tags:
new_config["leaderboard_auto_update_active"] = False
else:
new_config["leaderboard_auto_update_active"] = True
return new_config
def activate_leaderboard_config(
leaderboard_id: uuid.UUID,
):
"""
Add tag status:active to leaderboard config journal entry
"""
entry_config = get_leaderboard_config_entry(leaderboard_id)
if "status:active" in entry_config.tags:
raise LeaderboardConfigAlreadyActive(
f"Leaderboard config {leaderboard_id} already active"
)
bc.create_tags(
token=MOONSTREAM_ADMIN_ACCESS_TOKEN,
journal_id=MOONSTREAM_LEADERBOARD_CONFIGURATION_JOURNAL_ID,
entry_id=entry_config.entry_url.split("/")[-1],
tags=["status:active"],
)
def deactivate_leaderboard_config(
leaderboard_id: uuid.UUID,
):
"""
Remove tag status:active from leaderboard config journal entry
"""
entry_config = get_leaderboard_config_entry(leaderboard_id)
if "status:active" not in entry_config.tags:
raise LeaderboardConfigAlreadyInactive(
f"Leaderboard config {leaderboard_id} not active"
)
bc.delete_tag(
token=MOONSTREAM_ADMIN_ACCESS_TOKEN,
journal_id=MOONSTREAM_LEADERBOARD_CONFIGURATION_JOURNAL_ID,
entry_id=entry_config.entry_url.split("/")[-1],
tag="status:active",
)
def revoke_resource(
db_session: Session, leaderboard_id: uuid.UUID
) -> Optional[uuid.UUID]:
@ -1529,6 +1667,7 @@ def check_leaderboard_resource_permissions(
headers = {
"Authorization": f"Bearer {token}",
}
# If user don't have at least read permission return 404
result = requests.get(url=permission_url, headers=headers, timeout=10)

Wyświetl plik

@ -428,3 +428,17 @@ class LeaderboardDeletedResponse(BaseModel):
class LeaderboardScoresChangesResponse(BaseModel):
players_count: int
date: datetime
class LeaderboardConfig(BaseModel):
leaderboard_id: str
leaderboard_auto_update_active: bool = False
query_name: str
params: Dict[str, int]
normalize_addresses: bool
class LeaderboardConfigUpdate(BaseModel):
query_name: Optional[str] = None
params: Dict[str, int]
normalize_addresses: Optional[bool] = None

Wyświetl plik

@ -1,10 +1,10 @@
"""
Leaderboard API.
"""
from datetime import datetime
import logging
from uuid import UUID
from bugout.exceptions import BugoutResponseException
from web3 import Web3
from fastapi import FastAPI, Request, Depends, Response, Query, Path, Body, Header
from sqlalchemy.orm import Session
@ -668,3 +668,215 @@ async def leaderboard_push_scores(
]
return result
@app.get(
"/{leaderboard_id}/config",
response_model=data.LeaderboardConfig,
tags=["Authorized Endpoints"],
)
async def leaderboard_config(
request: Request,
leaderboard_id: UUID = Path(..., description="Leaderboard ID"),
db_session: Session = Depends(db.yield_db_session),
Authorization: str = AuthHeader,
) -> data.LeaderboardConfig:
"""
Get leaderboard config.
"""
token = request.state.token
try:
access = actions.check_leaderboard_resource_permissions(
db_session=db_session,
leaderboard_id=leaderboard_id,
token=token,
)
except NoResultFound as e:
raise EngineHTTPException(
status_code=404,
detail="Leaderboard not found.",
)
if not access:
raise EngineHTTPException(
status_code=403, detail="You don't have access to this leaderboard."
)
try:
leaderboard_config = actions.get_leaderboard_config(
leaderboard_id=leaderboard_id,
)
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.",
)
except Exception as e:
logger.error(f"Error while getting leaderboard config: {e}")
raise EngineHTTPException(status_code=500, detail="Internal server error")
return data.LeaderboardConfig(**leaderboard_config)
@app.put(
"/{leaderboard_id}/config",
response_model=data.LeaderboardConfig,
tags=["Authorized Endpoints"],
)
async def leaderboard_config_update(
request: Request,
leaderboard_id: UUID = Path(..., description="Leaderboard ID"),
config: data.LeaderboardConfigUpdate = Body(..., description="Leaderboard config."),
db_session: Session = Depends(db.yield_db_session),
Authorization: str = AuthHeader,
) -> data.LeaderboardConfig:
"""
Update leaderboard config.
"""
token = request.state.token
try:
access = actions.check_leaderboard_resource_permissions(
db_session=db_session,
leaderboard_id=leaderboard_id,
token=token,
)
except NoResultFound as e:
raise EngineHTTPException(
status_code=404,
detail="Leaderboard not found.",
)
if not access:
raise EngineHTTPException(
status_code=403, detail="You don't have access to this leaderboard."
)
try:
leaderboard_config = actions.update_leaderboard_config(
leaderboard_id=leaderboard_id,
config=config,
)
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.",
)
except Exception as e:
logger.error(f"Error while updating leaderboard config: {e}")
raise EngineHTTPException(status_code=500, detail="Internal server error")
return data.LeaderboardConfig(**leaderboard_config)
@app.post(
"/{leaderboard_id}/config/activate",
response_model=bool,
tags=["Authorized Endpoints"],
)
async def leaderboard_config_activate(
request: Request,
leaderboard_id: UUID = Path(..., description="Leaderboard ID"),
db_session: Session = Depends(db.yield_db_session),
Authorization: str = AuthHeader,
) -> bool:
"""
Activate leaderboard config.
"""
token = request.state.token
try:
access = actions.check_leaderboard_resource_permissions(
db_session=db_session,
leaderboard_id=leaderboard_id,
token=token,
)
except NoResultFound as e:
raise EngineHTTPException(
status_code=404,
detail="Leaderboard not found.",
)
if not access:
raise EngineHTTPException(
status_code=403, detail="You don't have access to this leaderboard."
)
try:
actions.activate_leaderboard_config(
leaderboard_id=leaderboard_id,
)
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.",
)
except actions.LeaderboardConfigAlreadyActive as e:
raise EngineHTTPException(
status_code=409,
detail="Leaderboard config is already active.",
)
except Exception as e:
logger.error(f"Error while activating leaderboard config: {e}")
raise EngineHTTPException(status_code=500, detail="Internal server error")
return True
@app.post(
"/{leaderboard_id}/config/deactivate",
response_model=bool,
tags=["Authorized Endpoints"],
)
async def leaderboard_config_deactivate(
request: Request,
leaderboard_id: UUID = Path(..., description="Leaderboard ID"),
db_session: Session = Depends(db.yield_db_session),
Authorization: str = AuthHeader,
) -> bool:
"""
Deactivate leaderboard config.
"""
token = request.state.token
try:
access = actions.check_leaderboard_resource_permissions(
db_session=db_session,
leaderboard_id=leaderboard_id,
token=token,
)
except NoResultFound as e:
raise EngineHTTPException(
status_code=404,
detail="Leaderboard not found.",
)
if not access:
raise EngineHTTPException(
status_code=403, detail="You don't have access to this leaderboard."
)
try:
actions.deactivate_leaderboard_config(
leaderboard_id=leaderboard_id,
)
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.",
)
except actions.LeaderboardConfigAlreadyInactive as e:
raise EngineHTTPException(
status_code=409,
detail="Leaderboard config is already inactive.",
)
except Exception as e:
logger.error(f"Error while deactivating leaderboard config: {e}")
raise EngineHTTPException(status_code=500, detail="Internal server error")
return True

Wyświetl plik

@ -198,3 +198,11 @@ if MOONSTREAM_ADMIN_ACCESS_TOKEN == "":
MOONSTREAM_ADMIN_ID = os.environ.get("MOONSTREAM_ADMIN_ID", "")
if MOONSTREAM_ADMIN_ID == "":
raise ValueError("MOONSTREAM_ADMIN_ID environment variable must be set")
MOONSTREAM_LEADERBOARD_CONFIGURATION_JOURNAL_ID = os.environ.get(
"MOONSTREAM_LEADERBOARD_CONFIGURATION_JOURNAL_ID", ""
)
if MOONSTREAM_LEADERBOARD_CONFIGURATION_JOURNAL_ID == "":
raise ValueError(
"MOONSTREAM_LEADERBOARD_CONFIGURATION_JOURNAL_ID environment variable must be set"
)

Wyświetl plik

@ -1 +1 @@
0.0.6
0.0.7

Wyświetl plik

@ -19,3 +19,6 @@ export MOONSTREAM_MUMBAI_WEB3_PROVIDER_URI="<JSON_RPC_API_URL>"
export MOONSTREAM_POLYGON_WEB3_PROVIDER_URI="<JSON_RPC_API_URL>"
export MOONSTREAM_XDAI_WEB3_PROVIDER_URI="<JSON_RPC_API_URL>"
export ENGINE_NODEBALANCER_ACCESS_ID="<access_id_for_Moonstream_Node_Balancer-if_provided_it_is_interpolated_into_provider_URIs>"
# leaderboard config
export MOONSTREAM_LEADERBOARD_CONFIGURATION_JOURNAL_ID="<config_journal_id>"