diff --git a/src/tgbot/handlers/treasury/commands.py b/src/tgbot/handlers/treasury/commands.py index ed4dc12..1e4a6f3 100644 --- a/src/tgbot/handlers/treasury/commands.py +++ b/src/tgbot/handlers/treasury/commands.py @@ -11,6 +11,7 @@ from src.tgbot.handlers.payments.purchase import PURCHASE_TOKEN_CALLBACK_DATA_PATTERN from src.tgbot.handlers.treasury.constants import PAYOUTS, TrxType from src.tgbot.handlers.treasury.service import ( + LEADERBOARD_WINDOW_DAYS, get_leaderboard, get_token_supply, get_user_balance, @@ -102,27 +103,30 @@ async def handle_show_leaderbaord(update: Update, context: ContextTypes.DEFAULT_ emoji = get_random_emoji() leaderboard = await get_leaderboard() - LEADERBOARD_TEXT = f"{emoji} Leaderboard {emoji}\n\n" + LEADERBOARD_TEXT = ( + f"{emoji} Leaderboard (last {LEADERBOARD_WINDOW_DAYS} days) {emoji}\n\n" + ) for i, user in enumerate(leaderboard): icon = "🏆" if i == 0 else "🥈" if i == 1 else "🥉" if i == 2 else "🏅" nick = user["nickname"] or get_random_emoji() * 3 - LEADERBOARD_TEXT += f"{icon} - {nick} - {user['balance']} 🍔\n" + weekly_earned = user.get("weekly_earned", 0) + LEADERBOARD_TEXT += f"{icon} - {nick} - {weekly_earned} 🍔\n" tokens = await get_token_supply() LEADERBOARD_TEXT += f"\nTotal supply: {tokens} 🍔" user_lb_data = await get_user_place_in_leaderboard(update.effective_user.id) if user_lb_data: - place, nickname, balance = ( + place, nickname, weekly_earned = ( user_lb_data["place"], user_lb_data["nickname"], - user_lb_data["balance"], + user_lb_data.get("weekly_earned", 0), ) if nickname: LEADERBOARD_TEXT += f""" You: -#{place} - {nickname} - {balance} 🍔 +#{place} - {nickname} - {weekly_earned} 🍔 /kitchen /uploads /chat """ diff --git a/src/tgbot/handlers/treasury/service.py b/src/tgbot/handlers/treasury/service.py index e524390..0dd07d3 100644 --- a/src/tgbot/handlers/treasury/service.py +++ b/src/tgbot/handlers/treasury/service.py @@ -7,6 +7,9 @@ from src.tgbot.handlers.treasury.constants import TrxType +LEADERBOARD_WINDOW_DAYS = 7 + + async def calculate_user_balance(user_id: int): select_statement = select(func.sum(treasury_trx.c.amount)).where( treasury_trx.c.user_id == user_id @@ -27,8 +30,31 @@ async def get_user_balance(user_id: int) -> int: return user_balance or 0 +def _recent_earnings_subquery(): + window_start = func.now() - text(f"INTERVAL '{LEADERBOARD_WINDOW_DAYS} days'") + + return ( + select( + treasury_trx.c.user_id.label("user_id"), + func.sum(treasury_trx.c.amount).label("weekly_earned"), + ) + .where(treasury_trx.c.created_at >= window_start) + .where(treasury_trx.c.amount > 0) + .group_by(treasury_trx.c.user_id) + .subquery() + ) + + async def get_leaderboard(limit=10) -> list[dict[str, Any]]: - select_statement = select(user).order_by(user.c.balance.desc()).limit(limit) + recent_earnings = _recent_earnings_subquery() + + select_statement = ( + select(user, recent_earnings.c.weekly_earned) + .join(recent_earnings, recent_earnings.c.user_id == user.c.id) + .order_by(recent_earnings.c.weekly_earned.desc(), user.c.id.asc()) + .limit(limit) + ) + return await fetch_all(select_statement) @@ -39,25 +65,31 @@ async def get_token_supply() -> int: async def get_user_place_in_leaderboard(user_id: int) -> int: - return await fetch_one( - text( - f""" - SELECT - id, nickname, place, balance - FROM ( - SELECT - ROW_NUMBER() OVER (ORDER BY balance DESC) place, - id, - nickname, - balance - FROM - "user" - ) with_row_number - WHERE id = {user_id} - """ + recent_earnings = _recent_earnings_subquery() + + ranked_users = ( + select( + user.c.id.label("id"), + user.c.nickname.label("nickname"), + user.c.balance.label("balance"), + recent_earnings.c.weekly_earned.label("weekly_earned"), + func.row_number() + .over( + order_by=( + recent_earnings.c.weekly_earned.desc(), + user.c.id.asc(), + ) + ) + .label("place"), ) + .join(recent_earnings, recent_earnings.c.user_id == user.c.id) + .subquery() ) + select_statement = select(ranked_users).where(ranked_users.c.id == user_id) + + return await fetch_one(select_statement) + async def create_treasury_trx( user_id: int,