PostgreSQL + async refactoring, WebSocket server, Redis caching, minified assets

This commit is contained in:
2026-07-25 13:31:40 +00:00
parent 47c7f17c90
commit 17da0b6d79
28 changed files with 1840 additions and 3244 deletions
+66 -58
View File
@@ -3,127 +3,135 @@ from typing import Optional
from jose import JWTError, jwt
from passlib.context import CryptContext
from fastapi import Depends, HTTPException, status, Request
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import Session
from database import get_db, User
from database import get_async_db, get_db, User
import os
SECRET_KEY = os.getenv("SECRET_KEY", "your-secret-key-change-this-in-production")
SECRET_KEY = os.getenv("SECRET_KEY", "supersecretkey-change-in-production")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 7 # 7 дней
ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 7
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login", auto_error=False)
def decode_token(token: str) -> Optional[dict]:
"""Декодирует JWT токен"""
if not token: # <-- Добавлена проверка
if not token:
return None
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return payload
return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
except JWTError:
return None
def verify_password(plain_password: str, hashed_password: str) -> bool:
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password: str) -> str:
return pwd_context.hash(password)
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
expire = datetime.utcnow() + (expires_delta or timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES))
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
async def get_current_user_from_token(token: str, db: Session) -> Optional[User]:
"""Получает пользователя из JWT токена"""
if not token: # <-- Добавлена проверка
async def get_current_user_from_token(token: str, db) -> Optional[User]:
if not token:
return None
payload = decode_token(token)
if not payload:
return None
username = payload.get("sub")
if not username:
return None
user = db.query(User).filter(User.username == username).first()
return user
if isinstance(db, AsyncSession):
result = await db.execute(select(User).where(User.username == username))
return result.scalar_one_or_none()
return db.query(User).filter(User.username == username).first()
async def get_current_user_optional(request: Request, db: Session = Depends(get_db)) -> Optional[User]:
async def get_current_user_optional(request: Request, db=Depends(get_async_db)) -> Optional[User]:
token = request.cookies.get("access_token")
if not token: # <-- Исправлено: сразу возвращаем None
if not token:
return None
if token.startswith("Bearer "):
token = token[7:]
return await get_current_user_from_token(token, db)
async def get_current_user(request: Request, db: Session = Depends(get_db)) -> User:
async def get_current_user(request: Request, db=Depends(get_async_db)) -> User:
token = request.cookies.get("access_token")
if not token:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Not authenticated",
headers={"WWW-Authenticate": "Bearer"},
)
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated")
if token.startswith("Bearer "):
token = token[7:]
user = await get_current_user_from_token(token, db)
if user is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials")
return user
async def get_current_admin(request: Request, db=Depends(get_async_db)) -> User:
user = await get_current_user(request, db)
if not user.is_admin:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required")
return user
# ─── Sync auth deps (for non-converted endpoints) ──────────────────────────
async def get_current_user_sync(request: Request, db=Depends(get_db)) -> User:
token = request.cookies.get("access_token")
if not token:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated")
if token.startswith("Bearer "):
token = token[7:]
user = db.query(User).filter(User.username == decode_token(token).get("sub")).first() if decode_token(token) else None
if user is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials")
return user
async def get_current_user_optional_sync(request: Request, db=Depends(get_db)) -> Optional[User]:
token = request.cookies.get("access_token")
if not token:
return None
if token.startswith("Bearer "):
token = token[7:]
payload = decode_token(token)
if not payload:
return None
return db.query(User).filter(User.username == payload.get("sub")).first()
def create_user(db: Session, username: str, password: str) -> User:
hashed_password = get_password_hash(password)
user = User(username=username, hashed_password=hashed_password, balance=1000.0)
hashed = get_password_hash(password)
user = User(username=username, hashed_password=hashed, balance=1000.0)
db.add(user)
db.commit()
db.refresh(user)
return user
def authenticate_user(db: Session, username: str, password: str) -> Optional[User]:
user = db.query(User).filter(User.username == username).first()
if not user:
return None
if not verify_password(password, user.hashed_password):
if not user or not verify_password(password, user.hashed_password):
return None
return user
def is_item_craftable(item_id: int) -> bool:
"""Проверяет, можно ли использовать предмет в контракте"""
from backend import get_item, is_final_in_collection
item = get_item(item_id)
if not item:
return False
if not item.get("is_craftable", True):
return False
if is_final_in_collection(item):
return False
return True
async def get_current_admin(request: Request, db: Session = Depends(get_db)) -> User:
"""Проверяет, что пользователь - админ"""
user = await get_current_user(request, db)
if not user.is_admin:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Admin access required"
)
return user