add dailyreward

This commit is contained in:
2025-12-13 00:51:35 +05:00
parent 66da0d0e27
commit cbec2203cd
2 changed files with 106 additions and 0 deletions

View File

@ -11,6 +11,7 @@ from datetime import datetime, timedelta
from app.models.server.event import PlayerEvent, OnlinePlayersUpdate from app.models.server.event import PlayerEvent, OnlinePlayersUpdate
from app.models.server.playtime import PlayerSession, PlayerPlaytime from app.models.server.playtime import PlayerSession, PlayerPlaytime
from app.services.coins import CoinsService from app.services.coins import CoinsService
from app.services.dailyreward import DailyRewardService
coins_service = CoinsService() coins_service = CoinsService()
@ -145,3 +146,15 @@ async def get_me(
Текущий пользователь по accessToken + clientToken. Текущий пользователь по accessToken + clientToken.
""" """
return await AuthService().get_current_user(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"])

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