token-storage-refactored
Some checks failed
Deploy on push / type-check (push) Failing after 8s
Deploy on push / deploy (push) Has been skipped

This commit is contained in:
2025-06-02 21:50:58 +03:00
parent cca2f71c59
commit 21d28a0d8b
33 changed files with 2934 additions and 1533 deletions

View File

@@ -150,12 +150,34 @@ def login_required(f: Callable) -> Callable:
)
logger.debug(f"[login_required] Заголовки: {req.headers if req else 'none'}")
# Извлекаем токен из заголовков для сохранения в контексте
token = None
if req:
# Проверяем заголовок с учетом регистра
headers_dict = dict(req.headers.items())
# Ищем заголовок Authorization независимо от регистра
for header_name, header_value in headers_dict.items():
if header_name.lower() == SESSION_TOKEN_HEADER.lower():
token = header_value
logger.debug(
f"[login_required] Найден заголовок {header_name}: {token[:10] if token else 'None'}..."
)
break
# Очищаем токен от префикса Bearer если он есть
if token and token.startswith("Bearer "):
token = token.split("Bearer ")[-1].strip()
# Для тестового режима: если req отсутствует, но в контексте есть author и roles
if not req and info.context.get("author") and info.context.get("roles"):
logger.debug("[login_required] Тестовый режим: используем данные из контекста")
user_id = info.context["author"]["id"]
user_roles = info.context["roles"]
is_admin = info.context.get("is_admin", False)
# В тестовом режиме токен может быть в контексте
if not token:
token = info.context.get("token")
else:
# Обычный режим: проверяем через HTTP заголовки
user_id, user_roles, is_admin = await check_auth(req)
@@ -179,6 +201,11 @@ def login_required(f: Callable) -> Callable:
# Проверяем права администратора
info.context["is_admin"] = is_admin
# Сохраняем токен в контексте для доступа в резолверах
if token:
info.context["token"] = token
logger.debug(f"[login_required] Токен сохранен в контексте: {token[:10] if token else 'None'}...")
# В тестовом режиме автор уже может быть в контексте
if (
not info.context.get("author")

View File

@@ -202,7 +202,7 @@ json_builder, json_array_builder, json_cast = get_json_builder()
# This function is used for search indexing
async def fetch_all_shouts(session: Session | None = None) -> list[Any]:
def fetch_all_shouts(session: Session | None = None) -> list[Any]:
"""Fetch all published shouts for search indexing with authors preloaded"""
from orm.shout import Shout
@@ -224,7 +224,12 @@ async def fetch_all_shouts(session: Session | None = None) -> list[Any]:
return []
finally:
if close_session:
session.close()
# Подавляем SQLAlchemy deprecated warning для синхронной сессии
import warnings
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
session.close()
def get_column_names_without_virtual(model_cls: type[BaseModel]) -> list[str]:

View File

@@ -245,6 +245,43 @@ class RedisService:
except Exception:
return False
async def execute_pipeline(self, commands: list[tuple[str, tuple[Any, ...]]]) -> list[Any]:
"""
Выполняет список команд через pipeline для лучшей производительности.
Избегает использования async context manager для pipeline чтобы избежать deprecated warnings.
Args:
commands: Список кортежей (команда, аргументы)
Returns:
Список результатов выполнения команд
"""
if not self.is_connected or self._client is None:
logger.warning("Redis not connected, cannot execute pipeline")
return []
try:
pipe = self.pipeline()
if pipe is None:
logger.error("Failed to create Redis pipeline")
return []
# Добавляем команды в pipeline
for command, args in commands:
cmd_method = getattr(pipe, command.lower(), None)
if cmd_method is not None:
cmd_method(*args)
else:
logger.error(f"Unknown Redis command in pipeline: {command}")
# Выполняем pipeline
results = await pipe.execute()
return results
except Exception as e:
logger.error(f"Redis pipeline execution failed: {e}")
return []
# Global Redis instance
redis = RedisService()

View File

@@ -651,7 +651,7 @@ class SearchService:
)
try:
results = await response.json()
results = response.json()
if not results or not isinstance(results, list):
return []