add dailyreward
This commit is contained in:
93
app/services/dailyreward.py
Normal file
93
app/services/dailyreward.py
Normal file
@ -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),
|
||||
}
|
||||
Reference in New Issue
Block a user