fix reward in dailyreward
This commit is contained in:
@ -1,86 +1,81 @@
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
from app.db.database import users_collection, db
|
from app.db.database import users_collection, db
|
||||||
|
|
||||||
coins_sessions_collection = db.coins_sessions
|
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:
|
class DailyRewardService:
|
||||||
async def claim_daily(self, username: str) -> dict:
|
async def claim_daily(self, username: str) -> dict:
|
||||||
now = datetime.utcnow()
|
now_utc = datetime.now(timezone.utc)
|
||||||
today = now.date()
|
today_local, start_today_utc, start_tomorrow_utc, _ = _day_bounds_utc(now_utc)
|
||||||
|
|
||||||
user = await users_collection.find_one({"username": username})
|
user = await users_collection.find_one({"username": username})
|
||||||
if not user:
|
if not user:
|
||||||
return {"claimed": False, "reason": "user_not_found"}
|
return {"claimed": False, "reason": "user_not_found"}
|
||||||
|
|
||||||
last_claim_at = user.get("daily_last_claim_at")
|
last_claim_at = user.get("daily_last_claim_at") # ожидаем datetime (лучше хранить UTC)
|
||||||
last_claim_day = last_claim_at.date() if last_claim_at else None
|
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:
|
||||||
if last_claim_day == today:
|
|
||||||
return {"claimed": False, "reason": "already_claimed_today", "streak": user.get("daily_streak", 0)}
|
return {"claimed": False, "reason": "already_claimed_today", "streak": user.get("daily_streak", 0)}
|
||||||
|
|
||||||
# вычисляем новый стрик
|
yesterday_local = today_local - timedelta(days=1)
|
||||||
yesterday = today - timedelta(days=1)
|
|
||||||
prev_streak = int(user.get("daily_streak", 0) or 0)
|
prev_streak = int(user.get("daily_streak", 0) or 0)
|
||||||
|
new_streak = (prev_streak + 1) if (last_local_day == yesterday_local) else 1
|
||||||
|
|
||||||
if last_claim_day == yesterday:
|
# твоя новая формула: 10, 20, 30 ... до 50 (кап)
|
||||||
new_streak = prev_streak + 1
|
reward = min(10 + (new_streak - 1) * 10, 50)
|
||||||
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(
|
result = await users_collection.update_one(
|
||||||
{
|
{
|
||||||
"username": username,
|
"username": username,
|
||||||
"$or": [
|
"$or": [
|
||||||
{"daily_last_claim_at": {"$exists": False}},
|
{"daily_last_claim_at": {"$exists": False}},
|
||||||
{"daily_last_claim_at": {"$lt": datetime(today.year, today.month, today.day)}},
|
{"daily_last_claim_at": {"$lt": start_today_utc.replace(tzinfo=None)}},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$inc": {"coins": reward},
|
"$inc": {"coins": reward},
|
||||||
"$set": {"daily_last_claim_at": now, "daily_streak": new_streak},
|
"$set": {"daily_last_claim_at": now_utc.replace(tzinfo=None), "daily_streak": new_streak},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if result.modified_count == 0:
|
if result.modified_count == 0:
|
||||||
# кто-то уже успел получить сегодня (гонка)
|
|
||||||
user2 = await users_collection.find_one({"username": username})
|
user2 = await users_collection.find_one({"username": username})
|
||||||
return {"claimed": False, "reason": "already_claimed_today", "streak": user2.get("daily_streak", 0)}
|
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({
|
await coins_sessions_collection.insert_one({
|
||||||
"player_name": username,
|
"player_name": username,
|
||||||
"update_type": "daily_login",
|
"update_type": "daily_login",
|
||||||
"timestamp": now,
|
"timestamp": now_utc.replace(tzinfo=None),
|
||||||
"coins_added": reward,
|
"coins_added": reward,
|
||||||
"streak": new_streak,
|
"streak": new_streak,
|
||||||
})
|
})
|
||||||
|
|
||||||
return {"claimed": True, "coins_added": reward, "streak": new_streak}
|
return {"claimed": True, "coins_added": reward, "streak": new_streak}
|
||||||
|
|
||||||
async def get_status(self, username: str) -> dict:
|
async def get_status(self, username: str) -> dict:
|
||||||
now = datetime.utcnow()
|
now_utc = datetime.now(timezone.utc)
|
||||||
today = now.date()
|
today_local, start_today_utc, start_tomorrow_utc, start_today_local = _day_bounds_utc(now_utc)
|
||||||
|
|
||||||
user = await users_collection.find_one({"username": username})
|
user = await users_collection.find_one({"username": username})
|
||||||
if not user:
|
if not user:
|
||||||
return {"ok": False, "reason": "user_not_found"}
|
return {"ok": False, "reason": "user_not_found"}
|
||||||
|
|
||||||
last_claim_at = user.get("daily_last_claim_at")
|
last_claim_at = user.get("daily_last_claim_at")
|
||||||
last_claim_day = last_claim_at.date() if last_claim_at else None
|
last_local_day = last_claim_at.replace(tzinfo=timezone.utc).astimezone(TZ).date() if last_claim_at else None
|
||||||
|
|
||||||
start_of_today = datetime(today.year, today.month, today.day)
|
can_claim = (last_local_day != today_local)
|
||||||
start_of_tomorrow = start_of_today + timedelta(days=1)
|
seconds_to_next = 0 if can_claim else int((start_tomorrow_utc - now_utc).total_seconds())
|
||||||
|
|
||||||
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:
|
if seconds_to_next < 0:
|
||||||
seconds_to_next = 0
|
seconds_to_next = 0
|
||||||
|
|
||||||
@ -88,6 +83,8 @@ class DailyRewardService:
|
|||||||
"ok": True,
|
"ok": True,
|
||||||
"can_claim": can_claim,
|
"can_claim": can_claim,
|
||||||
"seconds_to_next": seconds_to_next,
|
"seconds_to_next": seconds_to_next,
|
||||||
"next_claim_at": start_of_tomorrow.isoformat() + "Z",
|
# можно отдавать и UTC, и локальное время для UI:
|
||||||
|
"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),
|
"streak": int(user.get("daily_streak", 0) or 0),
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user