from datetime import datetime, timedelta, timezone from zoneinfo import ZoneInfo from app.db.database import users_collection, db coins_sessions_collection = db.coins_sessions TZ = ZoneInfo("Asia/Yekaterinburg") def _day_bounds_utc(now_utc: datetime): now_local = now_utc.astimezone(TZ) start_local = now_local.replace(hour=0, minute=0, second=0, microsecond=0) start_utc = start_local.astimezone(timezone.utc) next_utc = (start_local + timedelta(days=1)).astimezone(timezone.utc) return now_local.date(), start_utc, next_utc, start_local class DailyRewardService: async def claim_daily(self, username: str) -> dict: now_utc = datetime.now(timezone.utc) today_local, start_today_utc, start_tomorrow_utc, _ = _day_bounds_utc(now_utc) user = await users_collection.find_one({"username": username}) if not user: return {"claimed": False, "reason": "user_not_found"} was_online_today = await coins_sessions_collection.find_one( { "player_name": username, "update_type": "coins_update", "timestamp": { "$gte": start_today_utc.replace(tzinfo=None), "$lt": start_tomorrow_utc.replace(tzinfo=None), }, } ) if not was_online_today: return { "claimed": False, "reason": "not_online_today", "message": "Вы должны зайти на сервер сегодня, чтобы получить ежедневную награду", } last_claim_at = user.get("daily_last_claim_at") # ожидаем datetime (лучше хранить UTC) last_local_day = last_claim_at.replace(tzinfo=timezone.utc).astimezone(TZ).date() if last_claim_at else None if last_local_day == today_local: return {"claimed": False, "reason": "already_claimed_today", "streak": user.get("daily_streak", 0)} yesterday_local = today_local - timedelta(days=1) prev_streak = int(user.get("daily_streak", 0) or 0) new_streak = (prev_streak + 1) if (last_local_day == yesterday_local) else 1 # твоя новая формула: 10, 20, 30 ... до 50 (кап) reward = min(10 + (new_streak - 1) * 10, 50) result = await users_collection.update_one( { "username": username, "$or": [ {"daily_last_claim_at": {"$exists": False}}, {"daily_last_claim_at": {"$lt": start_today_utc.replace(tzinfo=None)}}, ], }, { "$inc": {"coins": reward}, "$set": {"daily_last_claim_at": now_utc.replace(tzinfo=None), "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)} await coins_sessions_collection.insert_one({ "player_name": username, "update_type": "daily_login", "timestamp": now_utc.replace(tzinfo=None), "coins_added": reward, "streak": new_streak, }) return {"claimed": True, "coins_added": reward, "streak": new_streak} async def get_status(self, username: str) -> dict: now_utc = datetime.now(timezone.utc) today_local, start_today_utc, start_tomorrow_utc, start_today_local = _day_bounds_utc(now_utc) user = await users_collection.find_one({"username": username}) if not user: return {"ok": False, "reason": "user_not_found"} was_online_today = await coins_sessions_collection.find_one({ "player_name": username, "update_type": "coins_update", "timestamp": { "$gte": start_today_utc.replace(tzinfo=None), "$lt": start_tomorrow_utc.replace(tzinfo=None), }, }) last_claim_at = user.get("daily_last_claim_at") last_local_day = last_claim_at.replace(tzinfo=timezone.utc).astimezone(TZ).date() if last_claim_at else None can_claim = (last_local_day != today_local) and bool(was_online_today) seconds_to_next = 0 if can_claim else int((start_tomorrow_utc - now_utc).total_seconds()) if seconds_to_next < 0: seconds_to_next = 0 return { "ok": True, "can_claim": can_claim, "was_online_today": bool(was_online_today), "seconds_to_next": seconds_to_next, "next_claim_at_utc": start_tomorrow_utc.isoformat().replace("+00:00", "Z"), "next_claim_at_local": (start_today_local + timedelta(days=1)).isoformat(), "streak": int(user.get("daily_streak", 0) or 0), } async def get_claim_days(self, username: str, limit: int = 60) -> dict: # Берём последние N записей daily_login и превращаем в список уникальных дней по ЕКБ cursor = coins_sessions_collection.find( {"player_name": username, "update_type": "daily_login"}, {"timestamp": 1, "_id": 0}, ).sort("timestamp", -1).limit(limit) days = [] seen = set() async for doc in cursor: ts = doc.get("timestamp") if not ts: continue # У тебя timestamp в Mongo — naive UTC (now_utc.replace(tzinfo=None)) :contentReference[oaicite:1]{index=1} ts_utc = ts.replace(tzinfo=timezone.utc) day_local = ts_utc.astimezone(TZ).date().isoformat() # YYYY-MM-DD по ЕКБ if day_local not in seen: seen.add(day_local) days.append(day_local) days.reverse() # чтобы было по возрастанию (старые → новые), если надо return {"ok": True, "days": days, "count": len(days)}