auth-wip
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
from math import ceil
|
||||
from sqlalchemy import or_
|
||||
from sqlalchemy import or_, cast, String
|
||||
from graphql.error import GraphQLError
|
||||
|
||||
from auth.decorators import admin_auth_required
|
||||
from services.db import local_session
|
||||
from services.schema import query
|
||||
from auth.orm import Author, Role
|
||||
from services.schema import query, mutation
|
||||
from auth.orm import Author, Role, AuthorRole
|
||||
from services.env import EnvManager, EnvVariable
|
||||
from utils.logger import root_logger as logger
|
||||
|
||||
|
||||
@@ -40,7 +41,7 @@ async def admin_get_users(_, info, limit=10, offset=0, search=None):
|
||||
or_(
|
||||
Author.email.ilike(search_term),
|
||||
Author.name.ilike(search_term),
|
||||
Author.id.cast(str).ilike(search_term),
|
||||
cast(Author.id, String).ilike(search_term),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -67,9 +68,7 @@ async def admin_get_users(_, info, limit=10, offset=0, search=None):
|
||||
if hasattr(user, "roles") and user.roles
|
||||
else [],
|
||||
"created_at": user.created_at,
|
||||
"last_seen": user.last_seen,
|
||||
"muted": user.muted or False,
|
||||
"is_active": not user.blocked if hasattr(user, "blocked") else True,
|
||||
"last_seen": user.last_seen
|
||||
}
|
||||
for user in users
|
||||
],
|
||||
@@ -120,3 +119,179 @@ async def admin_get_roles(_, info):
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении списка ролей: {str(e)}")
|
||||
raise GraphQLError(f"Не удалось получить список ролей: {str(e)}")
|
||||
|
||||
|
||||
@query.field("getEnvVariables")
|
||||
@admin_auth_required
|
||||
async def get_env_variables(_, info):
|
||||
"""
|
||||
Получает список переменных окружения, сгруппированных по секциям
|
||||
|
||||
Args:
|
||||
info: Контекст GraphQL запроса
|
||||
|
||||
Returns:
|
||||
Список секций с переменными окружения
|
||||
"""
|
||||
try:
|
||||
# Создаем экземпляр менеджера переменных окружения
|
||||
env_manager = EnvManager()
|
||||
|
||||
# Получаем все переменные
|
||||
sections = env_manager.get_all_variables()
|
||||
|
||||
# Преобразуем к формату GraphQL API
|
||||
result = [
|
||||
{
|
||||
"name": section.name,
|
||||
"description": section.description,
|
||||
"variables": [
|
||||
{
|
||||
"key": var.key,
|
||||
"value": var.value,
|
||||
"description": var.description,
|
||||
"type": var.type,
|
||||
"isSecret": var.is_secret,
|
||||
}
|
||||
for var in section.variables
|
||||
]
|
||||
}
|
||||
for section in sections
|
||||
]
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении переменных окружения: {str(e)}")
|
||||
raise GraphQLError(f"Не удалось получить переменные окружения: {str(e)}")
|
||||
|
||||
|
||||
@mutation.field("updateEnvVariable")
|
||||
@admin_auth_required
|
||||
async def update_env_variable(_, info, key, value):
|
||||
"""
|
||||
Обновляет значение переменной окружения
|
||||
|
||||
Args:
|
||||
info: Контекст GraphQL запроса
|
||||
key: Ключ переменной
|
||||
value: Новое значение
|
||||
|
||||
Returns:
|
||||
Boolean: результат операции
|
||||
"""
|
||||
try:
|
||||
# Создаем экземпляр менеджера переменных окружения
|
||||
env_manager = EnvManager()
|
||||
|
||||
# Обновляем переменную
|
||||
result = env_manager.update_variable(key, value)
|
||||
|
||||
if result:
|
||||
logger.info(f"Переменная окружения '{key}' успешно обновлена")
|
||||
else:
|
||||
logger.error(f"Не удалось обновить переменную окружения '{key}'")
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при обновлении переменной окружения: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
@mutation.field("updateEnvVariables")
|
||||
@admin_auth_required
|
||||
async def update_env_variables(_, info, variables):
|
||||
"""
|
||||
Массовое обновление переменных окружения
|
||||
|
||||
Args:
|
||||
info: Контекст GraphQL запроса
|
||||
variables: Список переменных для обновления
|
||||
|
||||
Returns:
|
||||
Boolean: результат операции
|
||||
"""
|
||||
try:
|
||||
# Создаем экземпляр менеджера переменных окружения
|
||||
env_manager = EnvManager()
|
||||
|
||||
# Преобразуем входные данные в формат для менеджера
|
||||
env_variables = [
|
||||
EnvVariable(
|
||||
key=var.get("key", ""),
|
||||
value=var.get("value", ""),
|
||||
type=var.get("type", "string")
|
||||
)
|
||||
for var in variables
|
||||
]
|
||||
|
||||
# Обновляем переменные
|
||||
result = env_manager.update_variables(env_variables)
|
||||
|
||||
if result:
|
||||
logger.info(f"Переменные окружения успешно обновлены ({len(variables)} шт.)")
|
||||
else:
|
||||
logger.error(f"Не удалось обновить переменные окружения")
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при массовом обновлении переменных окружения: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
@mutation.field("adminUpdateUser")
|
||||
@admin_auth_required
|
||||
async def admin_update_user(_, info, user):
|
||||
"""
|
||||
Обновляет роли пользователя
|
||||
|
||||
Args:
|
||||
info: Контекст GraphQL запроса
|
||||
user: Данные для обновления пользователя (содержит id и roles)
|
||||
|
||||
Returns:
|
||||
Boolean: результат операции
|
||||
"""
|
||||
try:
|
||||
user_id = user.get("id")
|
||||
roles = user.get("roles", [])
|
||||
|
||||
if not roles:
|
||||
logger.warning(f"Пользователю {user_id} не назначено ни одной роли. Доступ в систему будет заблокирован.")
|
||||
|
||||
with local_session() as session:
|
||||
# Получаем пользователя из базы данных
|
||||
author = session.query(Author).filter(Author.id == user_id).first()
|
||||
|
||||
if not author:
|
||||
logger.error(f"Пользователь с ID {user_id} не найден")
|
||||
return False
|
||||
|
||||
# Получаем текущие роли пользователя
|
||||
current_roles = {role.id for role in author.roles} if author.roles else set()
|
||||
|
||||
# Обновляем роли только если они изменились
|
||||
if set(roles) != current_roles:
|
||||
# Получаем все существующие роли, которые указаны для обновления
|
||||
role_objects = session.query(Role).filter(Role.id.in_(roles)).all()
|
||||
|
||||
# Очищаем текущие роли и добавляем новые
|
||||
author.roles = role_objects
|
||||
|
||||
# Сохраняем изменения в базе данных
|
||||
session.commit()
|
||||
|
||||
# Проверяем, добавлена ли пользователю роль reader
|
||||
has_reader = 'reader' in roles
|
||||
if not has_reader:
|
||||
logger.warning(f"Пользователю {author.email or author.id} не назначена роль 'reader'. Доступ в систему будет ограничен.")
|
||||
|
||||
logger.info(f"Роли пользователя {author.email or author.id} обновлены: {', '.join(roles)}")
|
||||
else:
|
||||
logger.info(f"Роли пользователя {author.email or author.id} не изменились")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
import traceback
|
||||
logger.error(f"Ошибка при обновлении ролей пользователя: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
@@ -40,6 +40,7 @@ async def get_current_user(_, info):
|
||||
author.last_seen = int(time.time())
|
||||
session.commit()
|
||||
|
||||
# Здесь можно не применять фильтрацию, так как пользователь получает свои данные
|
||||
return {"token": token, "author": author}
|
||||
|
||||
|
||||
@@ -76,6 +77,7 @@ async def confirm_email(_, info, token):
|
||||
session.add(user)
|
||||
session.commit()
|
||||
logger.info(f"[auth] confirmEmail: Email для пользователя {user_id} успешно подтвержден.")
|
||||
# Здесь можно не применять фильтрацию, так как пользователь получает свои данные
|
||||
return {"success": True, "token": session_token, "author": user, "error": None}
|
||||
except InvalidToken as e:
|
||||
logger.warning(f"[auth] confirmEmail: Невалидный токен - {e.message}")
|
||||
@@ -166,6 +168,7 @@ async def register_by_email(_, _info, email: str, password: str = "", name: str
|
||||
logger.info(
|
||||
f"[auth] registerUser: Пользователь {email} зарегистрирован, ссылка для подтверждения отправлена."
|
||||
)
|
||||
# При регистрации возвращаем данные самому пользователю, поэтому не фильтруем
|
||||
return {
|
||||
"success": True,
|
||||
"token": None,
|
||||
@@ -237,21 +240,52 @@ async def login(_, info, email: str, password: str):
|
||||
logger.info(
|
||||
f"[auth] login: Найден автор {email}, id={author.id}, имя={author.name}, пароль есть: {bool(author.password)}"
|
||||
)
|
||||
|
||||
# Проверяем наличие роли reader
|
||||
has_reader_role = False
|
||||
if hasattr(author, "roles") and author.roles:
|
||||
for role in author.roles:
|
||||
if role.id == "reader":
|
||||
has_reader_role = True
|
||||
break
|
||||
|
||||
# Если у пользователя нет роли reader и он не админ, запрещаем вход
|
||||
if not has_reader_role:
|
||||
# Проверяем, есть ли роль admin или super
|
||||
is_admin = author.email in ADMIN_EMAILS.split(",")
|
||||
|
||||
if not is_admin:
|
||||
logger.warning(f"[auth] login: У пользователя {email} нет роли 'reader', в доступе отказано")
|
||||
return {
|
||||
"success": False,
|
||||
"token": None,
|
||||
"author": None,
|
||||
"error": "У вас нет необходимых прав для входа. Обратитесь к администратору.",
|
||||
}
|
||||
|
||||
# Проверяем пароль
|
||||
# Проверяем пароль - важно использовать непосредственно объект author, а не его dict
|
||||
logger.info(f"[auth] login: НАЧАЛО ПРОВЕРКИ ПАРОЛЯ для {email}")
|
||||
verify_result = Identity.password(author, password)
|
||||
logger.info(
|
||||
f"[auth] login: РЕЗУЛЬТАТ ПРОВЕРКИ ПАРОЛЯ: {verify_result if isinstance(verify_result, dict) else 'успешно'}"
|
||||
)
|
||||
try:
|
||||
verify_result = Identity.password(author, password)
|
||||
logger.info(
|
||||
f"[auth] login: РЕЗУЛЬТАТ ПРОВЕРКИ ПАРОЛЯ: {verify_result if isinstance(verify_result, dict) else 'успешно'}"
|
||||
)
|
||||
|
||||
if isinstance(verify_result, dict) and verify_result.get("error"):
|
||||
logger.warning(f"[auth] login: Неверный пароль для {email}: {verify_result.get('error')}")
|
||||
if isinstance(verify_result, dict) and verify_result.get("error"):
|
||||
logger.warning(f"[auth] login: Неверный пароль для {email}: {verify_result.get('error')}")
|
||||
return {
|
||||
"success": False,
|
||||
"token": None,
|
||||
"author": None,
|
||||
"error": verify_result.get("error", "Ошибка авторизации"),
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"[auth] login: Ошибка при проверке пароля: {str(e)}")
|
||||
return {
|
||||
"success": False,
|
||||
"token": None,
|
||||
"author": None,
|
||||
"error": verify_result.get("error", "Ошибка авторизации"),
|
||||
"error": str(e),
|
||||
}
|
||||
|
||||
# Получаем правильный объект автора - результат verify_result
|
||||
@@ -346,9 +380,12 @@ async def login(_, info, email: str, password: str):
|
||||
if not cookie_set:
|
||||
logger.warning(f"[auth] login: Не удалось установить cookie никаким способом")
|
||||
|
||||
# Возвращаем успешный результат
|
||||
# Возвращаем успешный результат с данными для клиента
|
||||
# Для ответа клиенту используем dict() с параметром access=True,
|
||||
# чтобы получить полный доступ к данным для самого пользователя
|
||||
logger.info(f"[auth] login: Успешный вход для {email}")
|
||||
result = {"success": True, "token": token, "author": valid_author, "error": None}
|
||||
author_dict = valid_author.dict(access=True)
|
||||
result = {"success": True, "token": token, "author": author_dict, "error": None}
|
||||
logger.info(
|
||||
f"[auth] login: Возвращаемый результат: {{success: {result['success']}, token_length: {len(token) if token else 0}}}"
|
||||
)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import asyncio
|
||||
import time
|
||||
from typing import Optional
|
||||
from typing import Optional, List, Dict, Any
|
||||
|
||||
from sqlalchemy import select, text
|
||||
|
||||
@@ -26,11 +26,15 @@ DEFAULT_COMMUNITIES = [1]
|
||||
|
||||
|
||||
# Вспомогательная функция для получения всех авторов без статистики
|
||||
async def get_all_authors():
|
||||
async def get_all_authors(current_user_id=None):
|
||||
"""
|
||||
Получает всех авторов без статистики.
|
||||
Используется для случаев, когда нужен полный список авторов без дополнительной информации.
|
||||
|
||||
Args:
|
||||
current_user_id: ID текущего пользователя для проверки прав доступа
|
||||
is_admin: Флаг, указывающий, является ли пользователь администратором
|
||||
|
||||
Returns:
|
||||
list: Список всех авторов без статистики
|
||||
"""
|
||||
@@ -45,15 +49,15 @@ async def get_all_authors():
|
||||
authors_query = select(Author).where(Author.deleted_at.is_(None))
|
||||
authors = session.execute(authors_query).scalars().all()
|
||||
|
||||
# Преобразуем авторов в словари
|
||||
return [author.dict() for author in authors]
|
||||
# Преобразуем авторов в словари с учетом прав доступа
|
||||
return [author.dict(current_user_id, False) for author in authors]
|
||||
|
||||
# Используем универсальную функцию для кеширования запросов
|
||||
return await cached_query(cache_key, fetch_all_authors)
|
||||
|
||||
|
||||
# Вспомогательная функция для получения авторов со статистикой с пагинацией
|
||||
async def get_authors_with_stats(limit=50, offset=0, by: Optional[str] = None):
|
||||
async def get_authors_with_stats(limit=50, offset=0, by: Optional[str] = None, current_user_id: Optional[int] = None):
|
||||
"""
|
||||
Получает авторов со статистикой с пагинацией.
|
||||
|
||||
@@ -61,7 +65,7 @@ async def get_authors_with_stats(limit=50, offset=0, by: Optional[str] = None):
|
||||
limit: Максимальное количество возвращаемых авторов
|
||||
offset: Смещение для пагинации
|
||||
by: Опциональный параметр сортировки (new/active)
|
||||
|
||||
current_user_id: ID текущего пользователя
|
||||
Returns:
|
||||
list: Список авторов с их статистикой
|
||||
"""
|
||||
@@ -133,15 +137,18 @@ async def get_authors_with_stats(limit=50, offset=0, by: Optional[str] = None):
|
||||
# Формируем результат с добавлением статистики
|
||||
result = []
|
||||
for author in authors:
|
||||
# Получаем словарь с учетом прав доступа
|
||||
author_dict = author.dict()
|
||||
author_dict["stat"] = {
|
||||
"shouts": shouts_stats.get(author.id, 0),
|
||||
"followers": followers_stats.get(author.id, 0),
|
||||
}
|
||||
|
||||
result.append(author_dict)
|
||||
|
||||
# Кешируем каждого автора отдельно для использования в других функциях
|
||||
await cache_author(author_dict)
|
||||
# Важно: кэшируем полный словарь для админов
|
||||
await cache_author(author.dict())
|
||||
|
||||
return result
|
||||
|
||||
@@ -172,8 +179,8 @@ async def invalidate_authors_cache(author_id=None):
|
||||
# Получаем user_id автора, если есть
|
||||
with local_session() as session:
|
||||
author = session.query(Author).filter(Author.id == author_id).first()
|
||||
if author and author.user:
|
||||
specific_keys.append(f"author:user:{author.user.strip()}")
|
||||
if author and Author.id:
|
||||
specific_keys.append(f"author:user:{Author.id.strip()}")
|
||||
|
||||
# Удаляем конкретные ключи
|
||||
for key in specific_keys:
|
||||
@@ -198,24 +205,28 @@ async def invalidate_authors_cache(author_id=None):
|
||||
@login_required
|
||||
async def update_author(_, info, profile):
|
||||
user_id = info.context.get("user_id")
|
||||
is_admin = info.context.get("is_admin", False)
|
||||
|
||||
if not user_id:
|
||||
return {"error": "unauthorized", "author": None}
|
||||
try:
|
||||
with local_session() as session:
|
||||
author = session.query(Author).where(Author.user == user_id).first()
|
||||
author = session.query(Author).where(Author.id == user_id).first()
|
||||
if author:
|
||||
Author.update(author, profile)
|
||||
session.add(author)
|
||||
session.commit()
|
||||
author_query = select(Author).where(Author.user == user_id)
|
||||
author_query = select(Author).where(Author.id == user_id)
|
||||
result = get_with_stat(author_query)
|
||||
if result:
|
||||
author_with_stat = result[0]
|
||||
if isinstance(author_with_stat, Author):
|
||||
author_dict = author_with_stat.dict()
|
||||
# await cache_author(author_dict)
|
||||
# Кэшируем полную версию для админов
|
||||
author_dict = author_with_stat.dict(is_admin=True)
|
||||
asyncio.create_task(cache_author(author_dict))
|
||||
return {"error": None, "author": author}
|
||||
|
||||
# Возвращаем обычную полную версию, т.к. это владелец
|
||||
return {"error": None, "author": author}
|
||||
except Exception as exc:
|
||||
import traceback
|
||||
|
||||
@@ -224,24 +235,46 @@ async def update_author(_, info, profile):
|
||||
|
||||
|
||||
@query.field("get_authors_all")
|
||||
async def get_authors_all(_, _info):
|
||||
async def get_authors_all(_, info):
|
||||
"""
|
||||
Получает список всех авторов без статистики.
|
||||
|
||||
Returns:
|
||||
list: Список всех авторов
|
||||
"""
|
||||
return await get_all_authors()
|
||||
# Получаем ID текущего пользователя и флаг админа из контекста
|
||||
current_user_id = info.context.get("user_id") if hasattr(info, "context") else None
|
||||
authors = await get_all_authors(current_user_id, False)
|
||||
return authors
|
||||
|
||||
|
||||
@query.field("get_author")
|
||||
async def get_author(_, _info, slug="", author_id=0):
|
||||
async def get_author(_, info, slug="", author_id=0):
|
||||
# Получаем ID текущего пользователя и флаг админа из контекста
|
||||
current_user_id = info.context.get("user_id") if hasattr(info, "context") else None
|
||||
is_admin = info.context.get("is_admin", False) if hasattr(info, "context") else False
|
||||
|
||||
author_dict = None
|
||||
try:
|
||||
author_id = get_author_id_from(slug=slug, user="", author_id=author_id)
|
||||
if not author_id:
|
||||
raise ValueError("cant find")
|
||||
author_dict = await get_cached_author(int(author_id), get_with_stat)
|
||||
|
||||
# Получаем данные автора из кэша (полные данные)
|
||||
cached_author = await get_cached_author(int(author_id), get_with_stat)
|
||||
|
||||
# Применяем фильтрацию на стороне клиента, так как в кэше хранится полная версия
|
||||
if cached_author:
|
||||
# Создаем объект автора для использования метода dict
|
||||
temp_author = Author()
|
||||
for key, value in cached_author.items():
|
||||
if hasattr(temp_author, key):
|
||||
setattr(temp_author, key, value)
|
||||
# Получаем отфильтрованную версию
|
||||
author_dict = temp_author.dict(current_user_id, is_admin)
|
||||
# Добавляем статистику, которая могла быть в кэшированной версии
|
||||
if "stat" in cached_author:
|
||||
author_dict["stat"] = cached_author["stat"]
|
||||
|
||||
if not author_dict or not author_dict.get("stat"):
|
||||
# update stat from db
|
||||
@@ -250,9 +283,15 @@ async def get_author(_, _info, slug="", author_id=0):
|
||||
if result:
|
||||
author_with_stat = result[0]
|
||||
if isinstance(author_with_stat, Author):
|
||||
author_dict = author_with_stat.dict()
|
||||
# await cache_author(author_dict)
|
||||
asyncio.create_task(cache_author(author_dict))
|
||||
# Кэшируем полные данные для админов
|
||||
original_dict = author_with_stat.dict(is_admin=True)
|
||||
asyncio.create_task(cache_author(original_dict))
|
||||
|
||||
# Возвращаем отфильтрованную версию
|
||||
author_dict = author_with_stat.dict(current_user_id, is_admin)
|
||||
# Добавляем статистику
|
||||
if hasattr(author_with_stat, "stat"):
|
||||
author_dict["stat"] = author_with_stat.stat
|
||||
except ValueError:
|
||||
pass
|
||||
except Exception as exc:
|
||||
@@ -263,31 +302,43 @@ async def get_author(_, _info, slug="", author_id=0):
|
||||
|
||||
|
||||
@query.field("get_author_id")
|
||||
async def get_author_id(_, _info, user: str):
|
||||
async def get_author_id(_, info, user: str):
|
||||
# Получаем ID текущего пользователя и флаг админа из контекста
|
||||
current_user_id = info.context.get("user_id") if hasattr(info, "context") else None
|
||||
is_admin = info.context.get("is_admin", False) if hasattr(info, "context") else False
|
||||
|
||||
user_id = user.strip()
|
||||
logger.info(f"getting author id for {user_id}")
|
||||
author = None
|
||||
try:
|
||||
author = await get_cached_author_by_user_id(user_id, get_with_stat)
|
||||
if author:
|
||||
return author
|
||||
cached_author = await get_cached_author_by_user_id(user_id, get_with_stat)
|
||||
if cached_author:
|
||||
# Создаем объект автора для использования метода dict
|
||||
temp_author = Author()
|
||||
for key, value in cached_author.items():
|
||||
if hasattr(temp_author, key):
|
||||
setattr(temp_author, key, value)
|
||||
# Возвращаем отфильтрованную версию
|
||||
return temp_author.dict(current_user_id, is_admin)
|
||||
|
||||
author_query = select(Author).filter(Author.user == user_id)
|
||||
author_query = select(Author).filter(Author.id == user_id)
|
||||
result = get_with_stat(author_query)
|
||||
if result:
|
||||
author_with_stat = result[0]
|
||||
if isinstance(author_with_stat, Author):
|
||||
author_dict = author_with_stat.dict()
|
||||
# await cache_author(author_dict)
|
||||
asyncio.create_task(cache_author(author_dict))
|
||||
return author_with_stat
|
||||
# Кэшируем полную версию данных
|
||||
original_dict = author_with_stat.dict(is_admin=True)
|
||||
asyncio.create_task(cache_author(original_dict))
|
||||
|
||||
# Возвращаем отфильтрованную версию
|
||||
return author_with_stat.dict(current_user_id, is_admin)
|
||||
except Exception as exc:
|
||||
logger.error(f"Error getting author: {exc}")
|
||||
return None
|
||||
|
||||
|
||||
@query.field("load_authors_by")
|
||||
async def load_authors_by(_, _info, by, limit, offset):
|
||||
async def load_authors_by(_, info, by, limit, offset):
|
||||
"""
|
||||
Загружает авторов по заданному критерию с пагинацией.
|
||||
|
||||
@@ -299,8 +350,12 @@ async def load_authors_by(_, _info, by, limit, offset):
|
||||
Returns:
|
||||
list: Список авторов с учетом критерия
|
||||
"""
|
||||
# Получаем ID текущего пользователя и флаг админа из контекста
|
||||
current_user_id = info.context.get("user_id") if hasattr(info, "context") else None
|
||||
is_admin = info.context.get("is_admin", False) if hasattr(info, "context") else False
|
||||
|
||||
# Используем оптимизированную функцию для получения авторов
|
||||
return await get_authors_with_stats(limit, offset, by)
|
||||
return await get_authors_with_stats(limit, offset, by, current_user_id, is_admin)
|
||||
|
||||
|
||||
def get_author_id_from(slug="", user=None, author_id=None):
|
||||
@@ -316,7 +371,7 @@ def get_author_id_from(slug="", user=None, author_id=None):
|
||||
author_id = author.id
|
||||
return author_id
|
||||
if user:
|
||||
author = session.query(Author).filter(Author.user == user).first()
|
||||
author = session.query(Author).filter(Author.id == user).first()
|
||||
if author:
|
||||
author_id = author.id
|
||||
except Exception as exc:
|
||||
@@ -325,14 +380,30 @@ def get_author_id_from(slug="", user=None, author_id=None):
|
||||
|
||||
|
||||
@query.field("get_author_follows")
|
||||
async def get_author_follows(_, _info, slug="", user=None, author_id=0):
|
||||
async def get_author_follows(_, info, slug="", user=None, author_id=0):
|
||||
# Получаем ID текущего пользователя и флаг админа из контекста
|
||||
current_user_id = info.context.get("user_id") if hasattr(info, "context") else None
|
||||
is_admin = info.context.get("is_admin", False) if hasattr(info, "context") else False
|
||||
|
||||
logger.debug(f"getting follows for @{slug}")
|
||||
author_id = get_author_id_from(slug=slug, user=user, author_id=author_id)
|
||||
if not author_id:
|
||||
return {}
|
||||
|
||||
followed_authors = await get_cached_follower_authors(author_id)
|
||||
# Получаем данные из кэша
|
||||
followed_authors_raw = await get_cached_follower_authors(author_id)
|
||||
followed_topics = await get_cached_follower_topics(author_id)
|
||||
|
||||
# Фильтруем чувствительные данные авторов
|
||||
followed_authors = []
|
||||
for author_data in followed_authors_raw:
|
||||
# Создаем объект автора для использования метода dict
|
||||
temp_author = Author()
|
||||
for key, value in author_data.items():
|
||||
if hasattr(temp_author, key):
|
||||
setattr(temp_author, key, value)
|
||||
# Добавляем отфильтрованную версию
|
||||
followed_authors.append(temp_author.dict(current_user_id, is_admin))
|
||||
|
||||
# TODO: Get followed communities too
|
||||
return {
|
||||
@@ -354,18 +425,36 @@ async def get_author_follows_topics(_, _info, slug="", user=None, author_id=None
|
||||
|
||||
|
||||
@query.field("get_author_follows_authors")
|
||||
async def get_author_follows_authors(_, _info, slug="", user=None, author_id=None):
|
||||
async def get_author_follows_authors(_, info, slug="", user=None, author_id=None):
|
||||
# Получаем ID текущего пользователя и флаг админа из контекста
|
||||
current_user_id = info.context.get("user_id") if hasattr(info, "context") else None
|
||||
is_admin = info.context.get("is_admin", False) if hasattr(info, "context") else False
|
||||
|
||||
logger.debug(f"getting followed authors for @{slug}")
|
||||
author_id = get_author_id_from(slug=slug, user=user, author_id=author_id)
|
||||
if not author_id:
|
||||
return []
|
||||
followed_authors = await get_cached_follower_authors(author_id)
|
||||
|
||||
# Получаем данные из кэша
|
||||
followed_authors_raw = await get_cached_follower_authors(author_id)
|
||||
|
||||
# Фильтруем чувствительные данные авторов
|
||||
followed_authors = []
|
||||
for author_data in followed_authors_raw:
|
||||
# Создаем объект автора для использования метода dict
|
||||
temp_author = Author()
|
||||
for key, value in author_data.items():
|
||||
if hasattr(temp_author, key):
|
||||
setattr(temp_author, key, value)
|
||||
# Добавляем отфильтрованную версию
|
||||
followed_authors.append(temp_author.dict(current_user_id, is_admin))
|
||||
|
||||
return followed_authors
|
||||
|
||||
|
||||
def create_author(user_id: str, slug: str, name: str = ""):
|
||||
author = Author()
|
||||
author.user = user_id # Связь с user_id из системы авторизации
|
||||
Author.id = user_id # Связь с user_id из системы авторизации
|
||||
author.slug = slug # Идентификатор из системы авторизации
|
||||
author.created_at = author.updated_at = int(time.time())
|
||||
author.name = name or slug # если не указано
|
||||
@@ -377,10 +466,28 @@ def create_author(user_id: str, slug: str, name: str = ""):
|
||||
|
||||
|
||||
@query.field("get_author_followers")
|
||||
async def get_author_followers(_, _info, slug: str = "", user: str = "", author_id: int = 0):
|
||||
async def get_author_followers(_, info, slug: str = "", user: str = "", author_id: int = 0):
|
||||
# Получаем ID текущего пользователя и флаг админа из контекста
|
||||
current_user_id = info.context.get("user_id") if hasattr(info, "context") else None
|
||||
is_admin = info.context.get("is_admin", False) if hasattr(info, "context") else False
|
||||
|
||||
logger.debug(f"getting followers for author @{slug} or ID:{author_id}")
|
||||
author_id = get_author_id_from(slug=slug, user=user, author_id=author_id)
|
||||
if not author_id:
|
||||
return []
|
||||
followers = await get_cached_author_followers(author_id)
|
||||
|
||||
# Получаем данные из кэша
|
||||
followers_raw = await get_cached_author_followers(author_id)
|
||||
|
||||
# Фильтруем чувствительные данные авторов
|
||||
followers = []
|
||||
for follower_data in followers_raw:
|
||||
# Создаем объект автора для использования метода dict
|
||||
temp_author = Author()
|
||||
for key, value in follower_data.items():
|
||||
if hasattr(temp_author, key):
|
||||
setattr(temp_author, key, value)
|
||||
# Добавляем отфильтрованную версию
|
||||
followers.append(temp_author.dict(current_user_id, is_admin))
|
||||
|
||||
return followers
|
||||
|
@@ -71,7 +71,7 @@ async def create_invite(_, info, slug: str = "", author_id: int = 0):
|
||||
# Check if the inviter is the owner of the shout
|
||||
with local_session() as session:
|
||||
shout = session.query(Shout).filter(Shout.slug == slug).first()
|
||||
inviter = session.query(Author).filter(Author.user == user_id).first()
|
||||
inviter = session.query(Author).filter(Author.id == user_id).first()
|
||||
if inviter and shout and shout.authors and inviter.id is shout.created_by:
|
||||
# Check if an invite already exists
|
||||
existing_invite = (
|
||||
@@ -109,7 +109,7 @@ async def create_invite(_, info, slug: str = "", author_id: int = 0):
|
||||
async def remove_author(_, info, slug: str = "", author_id: int = 0):
|
||||
user_id = info.context["user_id"]
|
||||
with local_session() as session:
|
||||
author = session.query(Author).filter(Author.user == user_id).first()
|
||||
author = session.query(Author).filter(Author.id == user_id).first()
|
||||
if author:
|
||||
shout = session.query(Shout).filter(Shout.slug == slug).first()
|
||||
# NOTE: owner should be first in a list
|
||||
|
@@ -23,7 +23,7 @@ async def get_communities_by_author(_, _info, slug="", user="", author_id=0):
|
||||
author_id = session.query(Author).where(Author.slug == slug).first().id
|
||||
q = q.where(CommunityFollower.author == author_id)
|
||||
if user:
|
||||
author_id = session.query(Author).where(Author.user == user).first().id
|
||||
author_id = session.query(Author).where(Author.id == user).first().id
|
||||
q = q.where(CommunityFollower.author == author_id)
|
||||
if author_id:
|
||||
q = q.where(CommunityFollower.author == author_id)
|
||||
|
@@ -643,7 +643,7 @@ async def delete_shout(_, info, shout_id: int):
|
||||
for author in shout.authors:
|
||||
await cache_by_id(Author, author.id, cache_author)
|
||||
info.context["author"] = author.dict()
|
||||
info.context["user_id"] = author.user
|
||||
info.context["user_id"] = author.id
|
||||
unfollow(None, info, "shout", shout.slug)
|
||||
|
||||
for topic in shout.topics:
|
||||
|
@@ -63,7 +63,14 @@ async def follow(_, info, what, slug="", entity_id=0):
|
||||
return {"error": f"{what.lower()} not found"}
|
||||
if not entity_id and entity:
|
||||
entity_id = entity.id
|
||||
entity_dict = entity.dict()
|
||||
|
||||
# Если это автор, учитываем фильтрацию данных
|
||||
if what == "AUTHOR":
|
||||
# Полная версия для кэширования
|
||||
entity_dict = entity.dict(is_admin=True)
|
||||
else:
|
||||
entity_dict = entity.dict()
|
||||
|
||||
logger.debug(f"entity_id: {entity_id}, entity_dict: {entity_dict}")
|
||||
|
||||
if entity_id:
|
||||
@@ -96,7 +103,35 @@ async def follow(_, info, what, slug="", entity_id=0):
|
||||
if get_cached_follows_method:
|
||||
logger.debug("Получение подписок из кэша")
|
||||
existing_follows = await get_cached_follows_method(follower_id)
|
||||
follows = [*existing_follows, entity_dict] if not existing_sub else existing_follows
|
||||
|
||||
# Если это авторы, получаем безопасную версию
|
||||
if what == "AUTHOR":
|
||||
# Получаем ID текущего пользователя и фильтруем данные
|
||||
current_user_id = user_id
|
||||
follows_filtered = []
|
||||
|
||||
for author_data in existing_follows:
|
||||
# Создаем объект автора для использования метода dict
|
||||
temp_author = Author()
|
||||
for key, value in author_data.items():
|
||||
if hasattr(temp_author, key):
|
||||
setattr(temp_author, key, value)
|
||||
# Добавляем отфильтрованную версию
|
||||
follows_filtered.append(temp_author.dict(current_user_id, False))
|
||||
|
||||
if not existing_sub:
|
||||
# Создаем объект автора для entity_dict
|
||||
temp_author = Author()
|
||||
for key, value in entity_dict.items():
|
||||
if hasattr(temp_author, key):
|
||||
setattr(temp_author, key, value)
|
||||
# Добавляем отфильтрованную версию
|
||||
follows = [*follows_filtered, temp_author.dict(current_user_id, False)]
|
||||
else:
|
||||
follows = follows_filtered
|
||||
else:
|
||||
follows = [*existing_follows, entity_dict] if not existing_sub else existing_follows
|
||||
|
||||
logger.debug("Обновлен список подписок")
|
||||
|
||||
if what == "AUTHOR" and not existing_sub:
|
||||
@@ -171,11 +206,38 @@ async def unfollow(_, info, what, slug="", entity_id=0):
|
||||
|
||||
if cache_method:
|
||||
logger.debug("Обновление кэша после отписки")
|
||||
await cache_method(entity.dict())
|
||||
# Если это автор, кэшируем полную версию
|
||||
if what == "AUTHOR":
|
||||
await cache_method(entity.dict(is_admin=True))
|
||||
else:
|
||||
await cache_method(entity.dict())
|
||||
|
||||
if get_cached_follows_method:
|
||||
logger.debug("Получение подписок из кэша")
|
||||
existing_follows = await get_cached_follows_method(follower_id)
|
||||
follows = filter(lambda x: x["id"] != entity_id, existing_follows)
|
||||
|
||||
# Если это авторы, получаем безопасную версию
|
||||
if what == "AUTHOR":
|
||||
# Получаем ID текущего пользователя и фильтруем данные
|
||||
current_user_id = user_id
|
||||
follows_filtered = []
|
||||
|
||||
for author_data in existing_follows:
|
||||
if author_data["id"] == entity_id:
|
||||
continue
|
||||
|
||||
# Создаем объект автора для использования метода dict
|
||||
temp_author = Author()
|
||||
for key, value in author_data.items():
|
||||
if hasattr(temp_author, key):
|
||||
setattr(temp_author, key, value)
|
||||
# Добавляем отфильтрованную версию
|
||||
follows_filtered.append(temp_author.dict(current_user_id, False))
|
||||
|
||||
follows = follows_filtered
|
||||
else:
|
||||
follows = [item for item in existing_follows if item["id"] != entity_id]
|
||||
|
||||
logger.debug("Обновлен список подписок")
|
||||
|
||||
if what == "AUTHOR":
|
||||
|
@@ -215,7 +215,7 @@ async def set_featured(session, shout_id):
|
||||
session.commit()
|
||||
author = session.query(Author).filter(Author.id == s.created_by).first()
|
||||
if author:
|
||||
await add_user_role(str(author.user))
|
||||
await add_user_role(str(author.id))
|
||||
session.add(s)
|
||||
session.commit()
|
||||
|
||||
@@ -446,7 +446,7 @@ async def delete_reaction(_, info, reaction_id: int):
|
||||
|
||||
with local_session() as session:
|
||||
try:
|
||||
author = session.query(Author).filter(Author.user == user_id).one()
|
||||
author = session.query(Author).filter(Author.id == user_id).one()
|
||||
r = session.query(Reaction).filter(Reaction.id == reaction_id).one()
|
||||
|
||||
if r.created_by != author_id and "editor" not in roles:
|
||||
|
@@ -255,7 +255,7 @@ async def get_topics_by_author(_, _info, author_id=0, slug="", user=""):
|
||||
elif slug:
|
||||
topics_by_author_query = topics_by_author_query.join(Author).where(Author.slug == slug)
|
||||
elif user:
|
||||
topics_by_author_query = topics_by_author_query.join(Author).where(Author.user == user)
|
||||
topics_by_author_query = topics_by_author_query.join(Author).where(Author.id == user)
|
||||
|
||||
return get_with_stat(topics_by_author_query)
|
||||
|
||||
@@ -320,7 +320,7 @@ async def delete_topic(_, info, slug: str):
|
||||
t: Topic = session.query(Topic).filter(Topic.slug == slug).first()
|
||||
if not t:
|
||||
return {"error": "invalid topic slug"}
|
||||
author = session.query(Author).filter(Author.user == user_id).first()
|
||||
author = session.query(Author).filter(Author.id == user_id).first()
|
||||
if author:
|
||||
if t.created_by != author.id:
|
||||
return {"error": "access denied"}
|
||||
|
Reference in New Issue
Block a user