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), }