import os import secrets from fastapi import APIRouter, HTTPException, Body, Response from fastapi.params import Query from app.models.user import QrApprove, UserCreate, UserLogin, VerifyCode from app.models.request import ValidateRequest from app.services.auth import AuthService from app.db.database import users_collection, sessions_collection from app.db.database import db from datetime import datetime import json from fastapi import HTTPException from datetime import datetime, timedelta from app.models.server.event import PlayerEvent, OnlinePlayersUpdate from app.models.server.playtime import PlayerSession, PlayerPlaytime from app.services.coins import CoinsService from app.services.dailyreward import DailyRewardService from app.services.dailyquests import DailyQuestsService coins_service = CoinsService() qr_logins_collection = db.qr_logins router = APIRouter( tags=["Users"] ) @router.post("/auth/register") async def register(user: UserCreate): """Регистрация нового пользователя""" return await AuthService().register(user) @router.post("/auth/authenticate") async def authenticate(credentials: UserLogin): """Аутентификация пользователя""" return await AuthService().login(credentials) @router.post("/auth/validate") async def validate_token(request: ValidateRequest): is_valid = await AuthService().validate(request.accessToken, request.clientToken) return {"valid": is_valid} @router.post("/auth/refresh") async def refresh_token(access_token: str, client_token: str): result = await AuthService().refresh(access_token, client_token) if not result: raise HTTPException(status_code=401, detail="Invalid tokens") return result @router.get("/sessionserver/session/minecraft/profile/{uuid}") async def get_minecraft_profile(uuid: str, unsigned: bool = False): return await AuthService().get_minecraft_profile(uuid) @router.post("/sessionserver/session/minecraft/join") async def join_server(request_data: dict = Body(...)): try: await AuthService().join_server(request_data) return Response(status_code=204) except Exception as e: print("Error in join_server:", str(e)) raise @router.get("/sessionserver/session/minecraft/hasJoined") async def has_joined(username: str, serverId: str): return await AuthService().has_joined(username, serverId) @router.get("/users/{username}/coins") async def get_user_coins(username: str): coins_data = await coins_service.get_player_coins(username) if not coins_data: raise HTTPException(status_code=404, detail="User not found") return coins_data @router.get("/users") async def get_users(): """Получение списка всех пользователей""" users = await users_collection.find().to_list(1000) # Исключаем чувствительные данные перед отправкой safe_users = [] for user in users: safe_users.append({ "username": user["username"], "uuid": user["uuid"], "skin_url": user.get("skin_url"), "cloak_url": user.get("cloak_url"), "coins": user.get("coins", 0), "total_time_played": user.get("total_time_played", 0), "is_active": user.get("is_active", True) }) return {"users": safe_users, "count": len(safe_users)} @router.get("/users/{uuid}") async def get_user_by_uuid(uuid: str): """Получение пользователя по UUID""" user = await users_collection.find_one({"uuid": uuid}) if not user: # Пробуем разные форматы UUID if '-' in uuid: user = await users_collection.find_one({"uuid": uuid.replace('-', '')}) else: formatted_uuid = f"{uuid[:8]}-{uuid[8:12]}-{uuid[12:16]}-{uuid[16:20]}-{uuid[20:]}" user = await users_collection.find_one({"uuid": formatted_uuid}) if not user: raise HTTPException(status_code=404, detail="User not found") # Исключаем чувствительные данные safe_user = { "username": user["username"], "uuid": user["uuid"], "skin_url": user.get("skin_url"), "cloak_url": user.get("cloak_url"), "coins": user.get("coins", 0), "total_time_played": user.get("total_time_played", 0), "is_active": user.get("is_active", True), "created_at": user.get("created_at") } if "total_time_played" in safe_user: total_time = safe_user["total_time_played"] hours, remainder = divmod(total_time, 3600) minutes, seconds = divmod(remainder, 60) safe_user["total_time_formatted"] = f"{hours}ч {minutes}м {seconds}с" return safe_user @router.post("/auth/verify_code") async def verify_code(payload: VerifyCode): return await AuthService().verify_code( username=payload.username, code=payload.code, telegram_user_id=payload.telegram_user_id, telegram_username=payload.telegram_username, ) @router.post("/auth/generate_code") async def generate_code(username: str): return await AuthService().generate_code(username) @router.get("/auth/verification_status/{username}") async def get_verification_status(username: str): return await AuthService().get_verification_status(username) @router.get("/auth/me") async def get_me( accessToken: str = Query(...), clientToken: str = Query(...), ): """ Текущий пользователь по accessToken + clientToken. """ return await AuthService().get_current_user(accessToken, clientToken) @router.post("/auth/qr/init") async def qr_init(device_id: str | None = Query(default=None)): token = secrets.token_urlsafe(24) expires_at = datetime.utcnow() + timedelta(minutes=2) await qr_logins_collection.insert_one({ "token": token, "device_id": device_id, "status": "pending", "approved_username": None, "created_at": datetime.utcnow(), "expires_at": expires_at, }) # deep-link в бота BOT_USERNAME = os.getenv("TELEGRAM_BOT_USERNAME") qr_url = f"https://t.me/{BOT_USERNAME}?start=qr_{token}" return {"token": token, "qr_url": qr_url, "expires_at": expires_at.isoformat()} @router.post("/auth/qr/approve") async def qr_approve(payload: QrApprove): return await AuthService().approve_qr_login(payload.token, payload.telegram_user_id) @router.get("/auth/qr/status") async def qr_status(token: str = Query(...), device_id: str | None = Query(default=None)): return await AuthService().qr_status(token, device_id) ### 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"]) @router.get("/users/daily/days") async def daily_days( accessToken: str = Query(...), clientToken: str = Query(...), limit: int = Query(60, ge=1, le=365), ): me = await AuthService().get_current_user(accessToken, clientToken) return await DailyRewardService().get_claim_days(me["username"], limit=limit) ### daily quests @router.get("/users/daily-quests/status") async def daily_quests_status(accessToken: str = Query(...), clientToken: str = Query(...)): me = await AuthService().get_current_user(accessToken, clientToken) return await DailyQuestsService().get_status(me["username"]) @router.post("/users/daily-quests/claim") async def daily_quests_claim( quest_key: str = Query(...), accessToken: str = Query(...), clientToken: str = Query(...), ): me = await AuthService().get_current_user(accessToken, clientToken) return await DailyQuestsService().claim(me["username"], quest_key)