From cbec2203cd768b9609b95bf561fd3fdcaac78a48 Mon Sep 17 00:00:00 2001 From: DIKER0K Date: Sat, 13 Dec 2025 00:51:35 +0500 Subject: [PATCH] add dailyreward --- app/api/users.py | 13 ++++++ app/services/dailyreward.py | 93 +++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 app/services/dailyreward.py diff --git a/app/api/users.py b/app/api/users.py index b277c90..b7b2753 100644 --- a/app/api/users.py +++ b/app/api/users.py @@ -11,6 +11,7 @@ from datetime import datetime, timedelta from app.models.server.event import PlayerEvent, OnlinePlayersUpdate from app.models.server.playtime import PlayerSession, PlayerPlaytime from app.services.coins import CoinsService +from app.services.dailyreward import DailyRewardService coins_service = CoinsService() @@ -145,3 +146,15 @@ async def get_me( Текущий пользователь по accessToken + clientToken. """ return await AuthService().get_current_user(accessToken, clientToken) + +### daily reward + +@router.post("/users/daily/claim") +async def claim_daily(accessToken: str = Query(...), clientToken: str = Query(...)): + me = await AuthService().get_current_user(accessToken, clientToken) # :contentReference[oaicite:7]{index=7} + return await DailyRewardService().claim_daily(me["username"]) + +@router.get("/users/daily/status") +async def daily_status(accessToken: str = Query(...), clientToken: str = Query(...)): + me = await AuthService().get_current_user(accessToken, clientToken) + return await DailyRewardService().get_status(me["username"]) diff --git a/app/services/dailyreward.py b/app/services/dailyreward.py new file mode 100644 index 0000000..e1f91ca --- /dev/null +++ b/app/services/dailyreward.py @@ -0,0 +1,93 @@ +from datetime import datetime, timedelta +from app.db.database import users_collection, db + +coins_sessions_collection = db.coins_sessions + +class DailyRewardService: + async def claim_daily(self, username: str) -> dict: + now = datetime.utcnow() + today = now.date() + + user = await users_collection.find_one({"username": username}) + if not user: + return {"claimed": False, "reason": "user_not_found"} + + last_claim_at = user.get("daily_last_claim_at") + last_claim_day = last_claim_at.date() if last_claim_at else None + + # уже получал сегодня + if last_claim_day == today: + return {"claimed": False, "reason": "already_claimed_today", "streak": user.get("daily_streak", 0)} + + # вычисляем новый стрик + yesterday = today - timedelta(days=1) + prev_streak = int(user.get("daily_streak", 0) or 0) + + if last_claim_day == yesterday: + new_streak = prev_streak + 1 + else: + new_streak = 1 + + # формула награды (пример) + base = 10 + bonus = min(new_streak, 7) * 2 # кап на 7 дней + reward = base + bonus + + # КЛЮЧЕВО: атомарная защита от двойного клика/параллельных запросов + # обновляем только если last_claim_day != today + result = await users_collection.update_one( + { + "username": username, + "$or": [ + {"daily_last_claim_at": {"$exists": False}}, + {"daily_last_claim_at": {"$lt": datetime(today.year, today.month, today.day)}}, + ], + }, + { + "$inc": {"coins": reward}, + "$set": {"daily_last_claim_at": now, "daily_streak": new_streak}, + }, + ) + + if result.modified_count == 0: + # кто-то уже успел получить сегодня (гонка) + user2 = await users_collection.find_one({"username": username}) + return {"claimed": False, "reason": "already_claimed_today", "streak": user2.get("daily_streak", 0)} + + # лог в coins_sessions (у тебя уже так сделано для coins_update) :contentReference[oaicite:4]{index=4} + await coins_sessions_collection.insert_one({ + "player_name": username, + "update_type": "daily_login", + "timestamp": now, + "coins_added": reward, + "streak": new_streak, + }) + + return {"claimed": True, "coins_added": reward, "streak": new_streak} + + async def get_status(self, username: str) -> dict: + now = datetime.utcnow() + today = now.date() + + user = await users_collection.find_one({"username": username}) + if not user: + return {"ok": False, "reason": "user_not_found"} + + last_claim_at = user.get("daily_last_claim_at") + last_claim_day = last_claim_at.date() if last_claim_at else None + + start_of_today = datetime(today.year, today.month, today.day) + start_of_tomorrow = start_of_today + timedelta(days=1) + + can_claim = (last_claim_day != today) + seconds_to_next = 0 if can_claim else int((start_of_tomorrow - now).total_seconds()) + if seconds_to_next < 0: + seconds_to_next = 0 + + return { + "ok": True, + "can_claim": can_claim, + "seconds_to_next": seconds_to_next, + "next_claim_at": start_of_tomorrow.isoformat() + "Z", + "streak": int(user.get("daily_streak", 0) or 0), + }