This commit is contained in:
@@ -72,20 +72,31 @@ class AdminService:
|
||||
@staticmethod
|
||||
def get_user_roles(user: Author, community_id: int = 1) -> list[str]:
|
||||
"""Получает роли пользователя в сообществе"""
|
||||
from orm.community import CommunityAuthor # Явный импорт
|
||||
from settings import ADMIN_EMAILS as ADMIN_EMAILS_LIST
|
||||
|
||||
admin_emails = ADMIN_EMAILS_LIST.split(",") if ADMIN_EMAILS_LIST else []
|
||||
user_roles = []
|
||||
|
||||
with local_session() as session:
|
||||
# Получаем все CommunityAuthor для пользователя
|
||||
all_community_authors = session.query(CommunityAuthor).filter(CommunityAuthor.author_id == user.id).all()
|
||||
|
||||
# Сначала ищем точное совпадение по community_id
|
||||
community_author = (
|
||||
session.query(CommunityAuthor)
|
||||
.filter(CommunityAuthor.author_id == user.id, CommunityAuthor.community_id == community_id)
|
||||
.first()
|
||||
)
|
||||
|
||||
# Если точного совпадения нет, используем первый найденный CommunityAuthor
|
||||
if not community_author and all_community_authors:
|
||||
community_author = all_community_authors[0]
|
||||
|
||||
if community_author:
|
||||
user_roles = community_author.role_list
|
||||
# Проверяем, что roles не None и не пустая строка
|
||||
if community_author.roles is not None and community_author.roles.strip():
|
||||
user_roles = community_author.role_list
|
||||
|
||||
# Добавляем синтетическую роль для системных админов
|
||||
if user.email and user.email.lower() in [email.lower() for email in admin_emails]:
|
||||
@@ -188,7 +199,15 @@ class AdminService:
|
||||
community_author.set_roles(valid_roles)
|
||||
session.commit()
|
||||
logger.info(f"Пользователь {author.email or author.id} обновлен")
|
||||
return {"success": True}
|
||||
|
||||
# Возвращаем обновленного пользователя
|
||||
return {
|
||||
"success": True,
|
||||
"name": author.name,
|
||||
"email": author.email,
|
||||
"slug": author.slug,
|
||||
"roles": self.get_user_roles(author),
|
||||
}
|
||||
|
||||
# === ПУБЛИКАЦИИ ===
|
||||
|
||||
|
@@ -153,37 +153,54 @@ class AuthService:
|
||||
|
||||
def create_user(self, user_dict: dict[str, Any], community_id: int | None = None) -> Author:
|
||||
"""Создает нового пользователя с дефолтными ролями"""
|
||||
# Нормализуем email
|
||||
if "email" in user_dict:
|
||||
user_dict["email"] = user_dict["email"].lower()
|
||||
|
||||
# Проверяем уникальность email
|
||||
with local_session() as session:
|
||||
existing_user = session.query(Author).filter(Author.email == user_dict["email"]).first()
|
||||
if existing_user:
|
||||
# Если пользователь с таким email уже существует, возвращаем его
|
||||
logger.warning(f"Пользователь с email {user_dict['email']} уже существует")
|
||||
return existing_user
|
||||
|
||||
# Генерируем уникальный slug
|
||||
base_slug = user_dict.get("slug", generate_unique_slug(user_dict.get("name", user_dict.get("email", "user"))))
|
||||
|
||||
# Проверяем уникальность slug
|
||||
with local_session() as session:
|
||||
# Добавляем суффикс, если slug уже существует
|
||||
counter = 1
|
||||
unique_slug = base_slug
|
||||
while session.query(Author).filter(Author.slug == unique_slug).first():
|
||||
unique_slug = f"{base_slug}-{counter}"
|
||||
counter += 1
|
||||
|
||||
user_dict["slug"] = unique_slug
|
||||
|
||||
user = Author(**user_dict)
|
||||
target_community_id = community_id or 1
|
||||
target_community_id = int(community_id) if community_id is not None else 1
|
||||
|
||||
with local_session() as session:
|
||||
session.add(user)
|
||||
session.flush()
|
||||
session.flush() # Получаем ID пользователя
|
||||
|
||||
# Получаем сообщество для назначения ролей
|
||||
logger.debug(f"Ищем сообщество с ID {target_community_id}")
|
||||
community = session.query(Community).filter(Community.id == target_community_id).first()
|
||||
|
||||
# Отладочная информация
|
||||
all_communities = session.query(Community).all()
|
||||
logger.debug(f"Все сообщества в базе: {[c.id for c in all_communities]}")
|
||||
|
||||
if not community:
|
||||
logger.warning(f"Сообщество {target_community_id} не найдено, используем ID=1")
|
||||
target_community_id = 1
|
||||
community = session.query(Community).filter(Community.id == target_community_id).first()
|
||||
|
||||
if community:
|
||||
# Инициализируем права сообщества
|
||||
try:
|
||||
import asyncio
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(community.initialize_role_permissions())
|
||||
except Exception as e:
|
||||
logger.warning(f"Не удалось инициализировать права сообщества: {e}")
|
||||
|
||||
# Получаем дефолтные роли
|
||||
try:
|
||||
default_roles = community.get_default_roles()
|
||||
if not default_roles:
|
||||
default_roles = ["reader", "author"]
|
||||
except AttributeError:
|
||||
default_roles = ["reader", "author"]
|
||||
default_roles = community.get_default_roles() or ["reader", "author"]
|
||||
|
||||
# Создаем CommunityAuthor с ролями
|
||||
community_author = CommunityAuthor(
|
||||
@@ -197,7 +214,12 @@ class AuthService:
|
||||
follower = CommunityFollower(community=target_community_id, follower=int(user.id))
|
||||
session.add(follower)
|
||||
|
||||
logger.info(f"Пользователь {user.id} создан с ролями {default_roles}")
|
||||
logger.info(
|
||||
f"Пользователь {user.id} создан с ролями {default_roles} в сообществе {target_community_id}"
|
||||
)
|
||||
else:
|
||||
# Если сообщество не найдено, вызываем исключение
|
||||
raise ValueError("Сообщество не найдено")
|
||||
|
||||
session.commit()
|
||||
return user
|
||||
@@ -353,7 +375,7 @@ class AuthService:
|
||||
# Проверяем роли через новую систему CommunityAuthor
|
||||
from orm.community import get_user_roles_in_community
|
||||
|
||||
user_roles = get_user_roles_in_community(author.id, community_id=1)
|
||||
user_roles = get_user_roles_in_community(int(author.id), community_id=1)
|
||||
has_reader_role = "reader" in user_roles
|
||||
|
||||
logger.debug(f"Роли пользователя {email}: {user_roles}")
|
||||
@@ -676,7 +698,7 @@ class AuthService:
|
||||
stats["checked"] += 1
|
||||
|
||||
try:
|
||||
had_reader = await self.ensure_user_has_reader_role(author.id)
|
||||
had_reader = await self.ensure_user_has_reader_role(int(author.id))
|
||||
if not had_reader:
|
||||
stats["fixed"] += 1
|
||||
|
||||
|
@@ -1,102 +1,34 @@
|
||||
import builtins
|
||||
import logging
|
||||
import math
|
||||
import time
|
||||
import traceback
|
||||
import warnings
|
||||
from io import TextIOWrapper
|
||||
from typing import Any, ClassVar, Type, TypeVar, Union
|
||||
from typing import Any, TypeVar
|
||||
|
||||
import orjson
|
||||
import sqlalchemy
|
||||
from sqlalchemy import JSON, Column, Integer, create_engine, event, exc, func, inspect
|
||||
from sqlalchemy import create_engine, event, exc, func, inspect
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
from sqlalchemy.engine import Connection, Engine
|
||||
from sqlalchemy.orm import Session, configure_mappers, declarative_base, joinedload
|
||||
from sqlalchemy.orm import Session, configure_mappers, joinedload
|
||||
from sqlalchemy.pool import StaticPool
|
||||
|
||||
from orm.base import BaseModel
|
||||
from settings import DB_URL
|
||||
from utils.logger import root_logger as logger
|
||||
|
||||
# Global variables
|
||||
REGISTRY: dict[str, type["BaseModel"]] = {}
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Database configuration
|
||||
engine = create_engine(DB_URL, echo=False, poolclass=StaticPool if "sqlite" in DB_URL else None)
|
||||
ENGINE = engine # Backward compatibility alias
|
||||
|
||||
inspector = inspect(engine)
|
||||
# Session = sessionmaker(engine)
|
||||
configure_mappers()
|
||||
T = TypeVar("T")
|
||||
FILTERED_FIELDS = ["_sa_instance_state", "search_vector"]
|
||||
|
||||
# Создаем Base для внутреннего использования
|
||||
_Base = declarative_base()
|
||||
|
||||
# Create proper type alias for Base
|
||||
BaseType = Type[_Base] # type: ignore[valid-type]
|
||||
|
||||
|
||||
class BaseModel(_Base): # type: ignore[valid-type,misc]
|
||||
__abstract__ = True
|
||||
__allow_unmapped__ = True
|
||||
__table_args__: ClassVar[Union[dict[str, Any], tuple]] = {"extend_existing": True}
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
|
||||
def __init_subclass__(cls, **kwargs: Any) -> None:
|
||||
REGISTRY[cls.__name__] = cls
|
||||
super().__init_subclass__(**kwargs)
|
||||
|
||||
def dict(self, access: bool = False) -> builtins.dict[str, Any]:
|
||||
"""
|
||||
Конвертирует ORM объект в словарь.
|
||||
|
||||
Пропускает атрибуты, которые отсутствуют в объекте, но присутствуют в колонках таблицы.
|
||||
Преобразует JSON поля в словари.
|
||||
Добавляет синтетическое поле .stat, если оно существует.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Словарь с атрибутами объекта
|
||||
"""
|
||||
column_names = filter(lambda x: x not in FILTERED_FIELDS, self.__table__.columns.keys())
|
||||
data = {}
|
||||
try:
|
||||
for column_name in column_names:
|
||||
try:
|
||||
# Проверяем, существует ли атрибут в объекте
|
||||
if hasattr(self, column_name):
|
||||
value = getattr(self, column_name)
|
||||
# Проверяем, является ли значение JSON и декодируем его при необходимости
|
||||
if isinstance(value, (str, bytes)) and isinstance(
|
||||
self.__table__.columns[column_name].type, JSON
|
||||
):
|
||||
try:
|
||||
data[column_name] = orjson.loads(value)
|
||||
except (TypeError, orjson.JSONDecodeError) as e:
|
||||
logger.exception(f"Error decoding JSON for column '{column_name}': {e}")
|
||||
data[column_name] = value
|
||||
else:
|
||||
data[column_name] = value
|
||||
else:
|
||||
# Пропускаем атрибут, если его нет в объекте (может быть добавлен после миграции)
|
||||
logger.debug(f"Skipping missing attribute '{column_name}' for {self.__class__.__name__}")
|
||||
except AttributeError as e:
|
||||
logger.warning(f"Attribute error for column '{column_name}': {e}")
|
||||
# Добавляем синтетическое поле .stat если оно существует
|
||||
if hasattr(self, "stat"):
|
||||
data["stat"] = self.stat
|
||||
except Exception as e:
|
||||
logger.exception(f"Error occurred while converting object to dictionary: {e}")
|
||||
return data
|
||||
|
||||
def update(self, values: builtins.dict[str, Any]) -> None:
|
||||
for key, value in values.items():
|
||||
if hasattr(self, key):
|
||||
setattr(self, key, value)
|
||||
|
||||
|
||||
# make_searchable(Base.metadata)
|
||||
# Base.metadata.create_all(bind=engine)
|
||||
|
||||
@@ -326,7 +258,5 @@ def local_session(src: str = "") -> Session:
|
||||
return Session(bind=engine, expire_on_commit=False)
|
||||
|
||||
|
||||
# Export Base for backward compatibility
|
||||
Base = _Base
|
||||
# Also export the type for type hints
|
||||
__all__ = ["Base", "BaseModel", "BaseType", "engine", "local_session"]
|
||||
__all__ = ["engine", "local_session"]
|
||||
|
Reference in New Issue
Block a user