From 733977f56ed302935b1c578779ce36703c225cf9 Mon Sep 17 00:00:00 2001 From: DIKER0K Date: Thu, 17 Jul 2025 03:45:44 +0500 Subject: [PATCH] refactoring --- app/api/__init__.py | 0 app/api/capes.py | 12 ++ app/api/meta.py | 18 +++ app/api/skins.py | 12 ++ app/api/users.py | 47 +++++++ app/core/__init__.py | 0 app/core/config.py | 11 ++ app/db/__init__.py | 0 app/db/database.py | 8 ++ app/models/__init__.py | 0 app/models/cape.py | 4 + app/models/request.py | 5 + app/models/skin.py | 5 + auth/app/models.py => app/models/user.py | 19 +-- app/services/__init__.py | 0 {auth/app => app/services}/auth.py | 146 +--------------------- app/services/cape.py | 70 +++++++++++ app/services/skin.py | 82 ++++++++++++ app/utils/__init__.py | 0 auth/app/utils.py => app/utils/misc.py | 11 +- auth/app/database.py | 17 --- auth/app/generate_key.py | 19 --- auth/app/main.py | 124 ------------------ main.py | 53 +++----- auth/requirements.txt => requirements.txt | 0 25 files changed, 300 insertions(+), 363 deletions(-) create mode 100644 app/api/__init__.py create mode 100644 app/api/capes.py create mode 100644 app/api/meta.py create mode 100644 app/api/skins.py create mode 100644 app/api/users.py create mode 100644 app/core/__init__.py create mode 100644 app/core/config.py create mode 100644 app/db/__init__.py create mode 100644 app/db/database.py create mode 100644 app/models/__init__.py create mode 100644 app/models/cape.py create mode 100644 app/models/request.py create mode 100644 app/models/skin.py rename auth/app/models.py => app/models/user.py (56%) create mode 100644 app/services/__init__.py rename {auth/app => app/services}/auth.py (57%) create mode 100644 app/services/cape.py create mode 100644 app/services/skin.py create mode 100644 app/utils/__init__.py rename auth/app/utils.py => app/utils/misc.py (75%) delete mode 100644 auth/app/database.py delete mode 100644 auth/app/generate_key.py delete mode 100644 auth/app/main.py rename auth/requirements.txt => requirements.txt (100%) diff --git a/app/api/__init__.py b/app/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/api/capes.py b/app/api/capes.py new file mode 100644 index 0000000..15b6e44 --- /dev/null +++ b/app/api/capes.py @@ -0,0 +1,12 @@ +from fastapi import APIRouter, UploadFile, File +from app.services.cape import CapeService + +router = APIRouter(tags=["Capes"]) + +@router.post("/user/{username}/cape") +async def set_cape(username: str, cape_file: UploadFile = File(...)): + return await CapeService().set_cape(username, cape_file) + +@router.delete("/user/{username}/cape") +async def remove_cape(username: str): + return await CapeService().remove_cape(username) diff --git a/app/api/meta.py b/app/api/meta.py new file mode 100644 index 0000000..2e16373 --- /dev/null +++ b/app/api/meta.py @@ -0,0 +1,18 @@ +from fastapi import APIRouter + +router = APIRouter(tags=["Meta"]) + +@router.get("/") +def api_root(): + return { + "meta": { + "serverName": "Your Auth Server", + "implementationName": "FastAPI", + "implementationVersion": "1.0.0", + "links": { + "homepage": "https://your-server.com" + }, + }, + "skinDomains": ["147.78.65.214"], + "capeDomains": ["147.78.65.214"] + } diff --git a/app/api/skins.py b/app/api/skins.py new file mode 100644 index 0000000..0f59e71 --- /dev/null +++ b/app/api/skins.py @@ -0,0 +1,12 @@ +from fastapi import APIRouter, UploadFile, File, Form +from app.services.skin import SkinService + +router = APIRouter(tags=["Skins"]) + +@router.post("/user/{username}/skin") +async def set_skin(username: str, skin_file: UploadFile = File(...), skin_model: str = Form("classic")): + return await SkinService().set_skin(username, skin_file, skin_model) + +@router.delete("/user/{username}/skin") +async def remove_skin(username: str): + return await SkinService().remove_skin(username) diff --git a/app/api/users.py b/app/api/users.py new file mode 100644 index 0000000..c1564da --- /dev/null +++ b/app/api/users.py @@ -0,0 +1,47 @@ +from fastapi import APIRouter, HTTPException, Body, Response +from app.models.user import UserCreate, UserLogin +from app.models.request import ValidateRequest +from app.services.auth import AuthService + +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) diff --git a/app/core/__init__.py b/app/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/core/config.py b/app/core/config.py new file mode 100644 index 0000000..560f930 --- /dev/null +++ b/app/core/config.py @@ -0,0 +1,11 @@ +from dotenv import load_dotenv +import os +from pathlib import Path + +load_dotenv(dotenv_path=Path(__file__).parent.parent.parent / ".env") + +FILES_URL = os.getenv("FILES_URL") +MONGO_URI = os.getenv("MONGO_URI") +SECRET_KEY = os.getenv("SECRET_KEY") +ALGORITHM = "HS256" +ACCESS_TOKEN_EXPIRE_MINUTES = 1440 # 24 часа diff --git a/app/db/__init__.py b/app/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/db/database.py b/app/db/database.py new file mode 100644 index 0000000..6034d40 --- /dev/null +++ b/app/db/database.py @@ -0,0 +1,8 @@ +from motor.motor_asyncio import AsyncIOMotorClient +from app.core.config import MONGO_URI + +client = AsyncIOMotorClient(MONGO_URI) +db = client["minecraft_auth"] + +users_collection = db["users"] +sessions_collection = db["sessions"] diff --git a/app/models/__init__.py b/app/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/models/cape.py b/app/models/cape.py new file mode 100644 index 0000000..8980cad --- /dev/null +++ b/app/models/cape.py @@ -0,0 +1,4 @@ +from pydantic import BaseModel + +class CapeUpdate(BaseModel): + cape_url: str diff --git a/app/models/request.py b/app/models/request.py new file mode 100644 index 0000000..08b5434 --- /dev/null +++ b/app/models/request.py @@ -0,0 +1,5 @@ +from pydantic import BaseModel + +class ValidateRequest(BaseModel): + accessToken: str # camelCase + clientToken: str diff --git a/app/models/skin.py b/app/models/skin.py new file mode 100644 index 0000000..c2d8657 --- /dev/null +++ b/app/models/skin.py @@ -0,0 +1,5 @@ +from pydantic import BaseModel +from typing import Optional + +class SkinUpdate(BaseModel): + skin_model: Optional[str] = "classic" diff --git a/auth/app/models.py b/app/models/user.py similarity index 56% rename from auth/app/models.py rename to app/models/user.py index a5dcdfe..7283cba 100644 --- a/auth/app/models.py +++ b/app/models/user.py @@ -1,8 +1,7 @@ from pydantic import BaseModel, EmailStr -from typing import Optional from datetime import datetime +from typing import Optional -# Для запросов class UserCreate(BaseModel): username: str email: EmailStr @@ -12,31 +11,19 @@ class UserLogin(BaseModel): username: str password: str -# Для MongoDB class UserInDB(BaseModel): username: str email: EmailStr hashed_password: str uuid: str skin_url: Optional[str] = None - skin_model: Optional[str] = "classic" # "classic" или "slim" + skin_model: Optional[str] = "classic" cloak_url: Optional[str] = None is_active: bool = True created_at: datetime = datetime.utcnow() - + class Session(BaseModel): access_token: str client_token: str user_uuid: str expires_at: datetime - -class ValidateRequest(BaseModel): - accessToken: str # camelCase - clientToken: str - -class SkinUpdate(BaseModel): - skin_model: Optional[str] = "classic" # "classic" или "slim" - # Удаляем skin_url и skin_file, так как будем принимать файл напрямую - -class CapeUpdate(BaseModel): - cape_url: str \ No newline at end of file diff --git a/app/services/__init__.py b/app/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/auth/app/auth.py b/app/services/auth.py similarity index 57% rename from auth/app/auth.py rename to app/services/auth.py index cb144fc..e6543f5 100644 --- a/auth/app/auth.py +++ b/app/services/auth.py @@ -2,14 +2,14 @@ import base64 import json from fastapi import HTTPException, UploadFile from fastapi.responses import JSONResponse -from .models import UserLogin, UserInDB, Session, UserCreate, SkinUpdate, CapeUpdate -from .utils import ( +from app.models.user import UserLogin, UserInDB, UserCreate, Session +from app.utils.misc import ( verify_password, get_password_hash, create_access_token, decode_token, ) -from .database import users_collection, sessions_collection +from ..db.database import users_collection, sessions_collection import uuid from datetime import datetime, timedelta from cryptography.hazmat.primitives import serialization, hashes @@ -207,143 +207,3 @@ class AuthService: "value": textures_value }] if textures else [] } - - async def set_skin(self, username: str, skin_file: UploadFile, skin_model: str = "classic"): - """Установка или замена скина через загрузку файла""" - # Проверяем тип файла - if not skin_file.content_type.startswith('image/'): - raise HTTPException(status_code=400, detail="File must be an image") - - # Проверяем размер файла (максимум 2MB) - max_size = 2 * 1024 * 1024 # 2MB - contents = await skin_file.read() - if len(contents) > max_size: - raise HTTPException(status_code=400, detail="File too large (max 2MB)") - - # Удаляем старый скин, если есть - user = await users_collection.find_one({"username": username}) - if user and user.get("skin_url"): - from urllib.parse import urlparse - import os - old_url = user["skin_url"] - # Получаем имя файла из url - old_filename = os.path.basename(urlparse(old_url).path) - old_path = os.path.join("skins", old_filename) - if os.path.exists(old_path): - try: - os.remove(old_path) - except Exception: - pass - - # Создаем папку для скинов, если ее нет - from pathlib import Path - skin_dir = Path("skins") - skin_dir.mkdir(exist_ok=True) - - # Генерируем имя файла - skin_filename = f"{username}_{int(datetime.now().timestamp())}.png" - skin_path = skin_dir / skin_filename - - # Сохраняем файл - with open(skin_path, "wb") as f: - f.write(contents) - - # Обновляем запись пользователя - result = await users_collection.update_one( - {"username": username}, - {"$set": { - "skin_url": f"{FILES_URL}/skins/{skin_filename}", - "skin_model": skin_model - }} - ) - - if result.modified_count == 0: - raise HTTPException(status_code=404, detail="User not found") - return {"status": "success"} - - async def remove_skin(self, username: str): - """Удаление скина""" - user = await users_collection.find_one({"username": username}) - if not user: - raise HTTPException(status_code=404, detail="User not found") - - # Удаляем файл скина, если он существует - if user.get("skin_url") and user["skin_url"].startswith("/skins/"): - import os - try: - os.remove(f"skins/{user['skin_url'].split('/')[-1]}") - except: - pass - - result = await users_collection.update_one( - {"username": username}, - {"$unset": { - "skin_url": "", - "skin_model": "" - }} - ) - return {"status": "success"} - - async def set_cape(self, username: str, cape_file: UploadFile): - """Установка или замена плаща через загрузку файла (PNG или GIF)""" - if not cape_file.content_type.startswith('image/'): - raise HTTPException(status_code=400, detail="File must be an image") - - # Определяем расширение - ext = None - if cape_file.content_type == "image/png": - ext = "png" - elif cape_file.content_type == "image/gif": - ext = "gif" - else: - raise HTTPException(status_code=400, detail="Only PNG and GIF capes are supported") - - max_size = 2 * 1024 * 1024 # 2MB - contents = await cape_file.read() - if len(contents) > max_size: - raise HTTPException(status_code=400, detail="File too large (max 2MB)") - - # Удаляем старый плащ, если есть - user = await users_collection.find_one({"username": username}) - if user and user.get("cloak_url"): - from urllib.parse import urlparse - import os - old_url = user["cloak_url"] - old_filename = os.path.basename(urlparse(old_url).path) - old_path = os.path.join("capes", old_filename) - if os.path.exists(old_path): - try: - os.remove(old_path) - except Exception: - pass - - from pathlib import Path - cape_dir = Path("capes") - cape_dir.mkdir(exist_ok=True) - - cape_filename = f"{username}_{int(datetime.now().timestamp())}.{ext}" - cape_path = cape_dir / cape_filename - - with open(cape_path, "wb") as f: - f.write(contents) - - result = await users_collection.update_one( - {"username": username}, - {"$set": { - "cloak_url": f"{FILES_URL}/capes/{cape_filename}" - }} - ) - - if result.modified_count == 0: - raise HTTPException(status_code=404, detail="User not found") - return {"status": "success"} - - async def remove_cape(self, username: str): - """Удаление плаща""" - result = await users_collection.update_one( - {"username": username}, - {"$unset": {"cloak_url": ""}} - ) - if result.modified_count == 0: - raise HTTPException(status_code=404, detail="User not found") - return {"status": "success"} diff --git a/app/services/cape.py b/app/services/cape.py new file mode 100644 index 0000000..710a683 --- /dev/null +++ b/app/services/cape.py @@ -0,0 +1,70 @@ +from app.db.database import users_collection +from app.core.config import FILES_URL +from fastapi import HTTPException, UploadFile +from datetime import datetime + +class CapeService: + async def set_cape(self, username: str, cape_file: UploadFile): + """Установка или замена плаща через загрузку файла (PNG или GIF)""" + if not cape_file.content_type.startswith('image/'): + raise HTTPException(status_code=400, detail="File must be an image") + + # Определяем расширение + ext = None + if cape_file.content_type == "image/png": + ext = "png" + elif cape_file.content_type == "image/gif": + ext = "gif" + else: + raise HTTPException(status_code=400, detail="Only PNG and GIF capes are supported") + + max_size = 2 * 1024 * 1024 # 2MB + contents = await cape_file.read() + if len(contents) > max_size: + raise HTTPException(status_code=400, detail="File too large (max 2MB)") + + # Удаляем старый плащ, если есть + user = await users_collection.find_one({"username": username}) + if user and user.get("cloak_url"): + from urllib.parse import urlparse + import os + old_url = user["cloak_url"] + old_filename = os.path.basename(urlparse(old_url).path) + old_path = os.path.join("app/static/capes", old_filename) + if os.path.exists(old_path): + try: + os.remove(old_path) + except Exception: + pass + + # Создаем папку для плащей, если ее нет + from pathlib import Path + cape_dir = Path("app/static/capes") + cape_dir.mkdir(parents=True, exist_ok=True) + + cape_filename = f"{username}_{int(datetime.now().timestamp())}.{ext}" + cape_path = cape_dir / cape_filename + + with open(cape_path, "wb") as f: + f.write(contents) + + result = await users_collection.update_one( + {"username": username}, + {"$set": { + "cloak_url": f"{FILES_URL}/capes/{cape_filename}" + }} + ) + + if result.modified_count == 0: + raise HTTPException(status_code=404, detail="User not found") + return {"status": "success"} + + async def remove_cape(self, username: str): + """Удаление плаща""" + result = await users_collection.update_one( + {"username": username}, + {"$unset": {"cloak_url": ""}} + ) + if result.modified_count == 0: + raise HTTPException(status_code=404, detail="User not found") + return {"status": "success"} diff --git a/app/services/skin.py b/app/services/skin.py new file mode 100644 index 0000000..774065b --- /dev/null +++ b/app/services/skin.py @@ -0,0 +1,82 @@ +from fastapi import HTTPException, UploadFile +from datetime import datetime +from app.db.database import users_collection +from app.core.config import FILES_URL + +class SkinService: + async def set_skin(self, username: str, skin_file: UploadFile, skin_model: str = "classic"): + """Установка или замена скина через загрузку файла""" + # Проверяем тип файла + if not skin_file.content_type.startswith('image/'): + raise HTTPException(status_code=400, detail="File must be an image") + + # Проверяем размер файла (максимум 2MB) + max_size = 2 * 1024 * 1024 # 2MB + contents = await skin_file.read() + if len(contents) > max_size: + raise HTTPException(status_code=400, detail="File too large (max 2MB)") + + # Удаляем старый скин, если есть + user = await users_collection.find_one({"username": username}) + if user and user.get("skin_url"): + from urllib.parse import urlparse + import os + old_url = user["skin_url"] + # Получаем имя файла из url + old_filename = os.path.basename(urlparse(old_url).path) + old_path = os.path.join("app/static/skins", old_filename) + print(f"Trying to delete old skin at: {old_path}") + if os.path.exists(old_path): + try: + os.remove(old_path) + except Exception: + pass + + # Создаем папку для скинов, если ее нет + from pathlib import Path + skin_dir = Path("app/static/skins") + skin_dir.mkdir(parents=True, exist_ok=True) + + # Генерируем имя файла + skin_filename = f"{username}_{int(datetime.now().timestamp())}.png" + skin_path = skin_dir / skin_filename + + # Сохраняем файл + with open(skin_path, "wb") as f: + f.write(contents) + + # Обновляем запись пользователя + result = await users_collection.update_one( + {"username": username}, + {"$set": { + "skin_url": f"{FILES_URL}/skins/{skin_filename}", + "skin_model": skin_model + }} + ) + + if result.modified_count == 0: + raise HTTPException(status_code=404, detail="User not found") + return {"status": "success"} + + async def remove_skin(self, username: str): + """Удаление скина""" + user = await users_collection.find_one({"username": username}) + if not user: + raise HTTPException(status_code=404, detail="User not found") + + # Удаляем файл скина, если он существует + if user.get("skin_url") and user["skin_url"].startswith("/skins/"): + import os + try: + os.remove(f"skins/{user['skin_url'].split('/')[-1]}") + except: + pass + + result = await users_collection.update_one( + {"username": username}, + {"$unset": { + "skin_url": "", + "skin_model": "" + }} + ) + return {"status": "success"} diff --git a/app/utils/__init__.py b/app/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/auth/app/utils.py b/app/utils/misc.py similarity index 75% rename from auth/app/utils.py rename to app/utils/misc.py index 82db937..78ea992 100644 --- a/auth/app/utils.py +++ b/app/utils/misc.py @@ -1,16 +1,7 @@ from jose import jwt, JWTError from passlib.context import CryptContext from datetime import datetime, timedelta -import os -from pathlib import Path -from dotenv import load_dotenv - -env_path = Path(__file__).parent.parent / ".env" -load_dotenv(dotenv_path=env_path) - -SECRET_KEY = os.getenv("SECRET_KEY") -ALGORITHM = "HS256" -ACCESS_TOKEN_EXPIRE_MINUTES = 1440 # 24 часа +from app.core.config import SECRET_KEY, ALGORITHM, ACCESS_TOKEN_EXPIRE_MINUTES pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") diff --git a/auth/app/database.py b/auth/app/database.py deleted file mode 100644 index 027fd4a..0000000 --- a/auth/app/database.py +++ /dev/null @@ -1,17 +0,0 @@ -from motor.motor_asyncio import AsyncIOMotorClient -from dotenv import load_dotenv -import os -from pathlib import Path - -env_path = Path(__file__).parent.parent / ".env" -load_dotenv(dotenv_path=env_path) - -MONGO_URI = os.getenv("MONGO_URI") -DB_NAME = "minecraft_auth" - -client = AsyncIOMotorClient(MONGO_URI) -db = client[DB_NAME] - -# Коллекции -users_collection = db["users"] -sessions_collection = db["sessions"] diff --git a/auth/app/generate_key.py b/auth/app/generate_key.py deleted file mode 100644 index 42ef134..0000000 --- a/auth/app/generate_key.py +++ /dev/null @@ -1,19 +0,0 @@ -from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.hazmat.primitives import serialization - -# Генерация ключа -private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) - -# Сохранение в PEM-формат -with open("private_key.pem", "wb") as f: - f.write(private_key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.PKCS8, - encryption_algorithm=serialization.NoEncryption() - )) - -with open("public_key.pem", "wb") as f: - f.write(private_key.public_key().public_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PublicFormat.SubjectPublicKeyInfo - )) \ No newline at end of file diff --git a/auth/app/main.py b/auth/app/main.py deleted file mode 100644 index c1dc637..0000000 --- a/auth/app/main.py +++ /dev/null @@ -1,124 +0,0 @@ -import base64 -from datetime import datetime -import json -from fastapi import FastAPI, Depends, File, Form, HTTPException, Body, Request, Response, UploadFile -from fastapi.security import OAuth2PasswordBearer -from fastapi.staticfiles import StaticFiles -from .models import UserCreate, UserLogin, ValidateRequest, SkinUpdate, CapeUpdate -from .auth import AuthService -from .database import users_collection -from .utils import decode_token -import os -from pathlib import Path -from typing import Union -from fastapi.middleware.cors import CORSMiddleware -import logging -# logging.basicConfig(level=logging.DEBUG) - -app = FastAPI() -auth_service = AuthService() - -skin_dir = Path("skins") -skin_dir.mkdir(exist_ok=True) -app.mount("/skins", StaticFiles(directory="skins"), name="skins") - -cape_dir = Path("capes") -cape_dir.mkdir(exist_ok=True) -app.mount("/capes", StaticFiles(directory="capes"), name="capes") - -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], # Разрешить все домены - allow_methods=["*"], - allow_headers=["*"], -) - -@app.get("/") -def api_root(): - return { - "meta": { - "serverName": "Your Auth Server", - "implementationName": "FastAPI", - "implementationVersion": "1.0.0", - "links": { - "homepage": "https://your-server.com" - }, - }, - "skinDomains": ["147.78.65.214"], - "capeDomains": ["147.78.65.214"] - } - -# Эндпоинты Mojang-like API -@app.post("/auth/register") -async def register(user: UserCreate): - return await auth_service.register(user) - -@app.post("/auth/authenticate") -async def authenticate(credentials: UserLogin): - return await auth_service.login(credentials) - -@app.post("/auth/validate") -async def validate_token(request: ValidateRequest): - is_valid = await auth_service.validate(request.accessToken, request.clientToken) - return {"valid": is_valid} - -@app.post("/auth/refresh") -async def refresh_token(access_token: str, client_token: str): - result = await auth_service.refresh(access_token, client_token) - if not result: - raise HTTPException(status_code=401, detail="Invalid tokens") - return result - -@app.get("/sessionserver/session/minecraft/profile/{uuid}") -async def get_minecraft_profile(uuid: str, unsigned: bool = False): - return await auth_service.get_minecraft_profile(uuid) - -@app.post("/sessionserver/session/minecraft/join") -async def join_server(request_data: dict = Body(...)): - try: - await auth_service.join_server(request_data) - return Response(status_code=204) - except Exception as e: - print("Error in join_server:", str(e)) - raise - -@app.get("/sessionserver/session/minecraft/hasJoined") -async def has_joined(username: str, serverId: str): - return await auth_service.has_joined(username, serverId) - - -@app.post("/user/{username}/skin") -async def set_skin( - username: str, - skin_file: UploadFile = File(...), - skin_model: str = Form("classic") -): - return await auth_service.set_skin(username, skin_file, skin_model) - -@app.delete("/user/{username}/skin") -async def remove_skin(username: str): - return await auth_service.remove_skin(username) - -@app.post("/user/{username}/cape") -async def set_cape( - username: str, - cape_file: UploadFile = File(...) -): - return await auth_service.set_cape(username, cape_file) - -@app.delete("/user/{username}/cape") -async def remove_cape(username: str): - return await auth_service.remove_cape(username) - -@app.get("/debug/profile/{uuid}") -async def debug_profile(uuid: str): - profile = await auth_service.get_minecraft_profile(uuid) - textures = base64.b64decode(profile['properties'][0]['value']).decode() - return { - "profile": profile, - "textures_decoded": json.loads(textures) - } - -if __name__ == "__main__": - import uvicorn - uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/main.py b/main.py index fdd2003..1f4035a 100644 --- a/main.py +++ b/main.py @@ -1,40 +1,25 @@ -from fastapi import FastAPI, HTTPException -from aiomcrcon import Client, RCONConnectionError, IncorrectPasswordError -import asyncio +from fastapi import FastAPI +from fastapi.staticfiles import StaticFiles +from app.api import users, skins, capes, meta +from fastapi.middleware.cors import CORSMiddleware app = FastAPI() -# Конфигурация RCON (замените на свои данные) -RCON_CONFIG = { - "hub": {"host": "minecraft.hub.popa-popa.ru", "port": 29001, "password": "2006siT_"}, - "survival": {"host": "minecraft.survival.popa-popa.ru", "port": 25575, "password": "пароль_survival"}, - "pillars": {"host": "minecraft.pillars.popa-popa.ru", "port": 29003, "password": "2006siT_"}, - "velocity": {"host": "minecraft.velocity.popa-popa.ru", "port": 25575, "password": "пароль_velocity"} -} +app.include_router(meta.router) +app.include_router(users.router) +app.include_router(skins.router) +app.include_router(capes.router) -async def send_rcon_command(server_type: str, command: str) -> str: - """Отправляет RCON-команду на указанный сервер.""" - config = RCON_CONFIG.get(server_type) - if not config: - raise HTTPException(status_code=400, detail="Неверный тип сервера") +# Монтируем статику +app.mount("/skins", StaticFiles(directory="app/static/skins"), name="skins") +app.mount("/capes", StaticFiles(directory="app/static/capes"), name="capes") - try: - async with Client(config["host"], config["port"], config["password"]) as client: - response = await client.send_cmd(command) - return response - except RCONConnectionError: - raise HTTPException(status_code=503, detail="Не удалось подключиться к серверу") - except IncorrectPasswordError: - raise HTTPException(status_code=403, detail="Неверный пароль RCON") +# CORS, middleware и т.д. -@app.get("/rcon/") -async def execute_rcon(server_type: str, command: str): - """Выполняет RCON-команду на указанном сервере.""" - result = await send_rcon_command(server_type, command) - return {"server": server_type, "command": command, "response": result} - -@app.get("/players/online/") -async def get_online_players(server_type: str): - """Возвращает список игроков онлайн на сервере.""" - players = await send_rcon_command(server_type, "list") - return {"server": server_type, "online_players": players} \ No newline at end of file +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) diff --git a/auth/requirements.txt b/requirements.txt similarity index 100% rename from auth/requirements.txt rename to requirements.txt