This commit is contained in:
@@ -24,7 +24,7 @@ class BaseModel(DeclarativeBase):
|
||||
REGISTRY[cls.__name__] = cls
|
||||
super().__init_subclass__(**kwargs)
|
||||
|
||||
def dict(self, access: bool = False) -> builtins.dict[str, Any]:
|
||||
def dict(self) -> builtins.dict[str, Any]:
|
||||
"""
|
||||
Конвертирует ORM объект в словарь.
|
||||
|
||||
@@ -44,7 +44,7 @@ class BaseModel(DeclarativeBase):
|
||||
if hasattr(self, column_name):
|
||||
value = getattr(self, column_name)
|
||||
# Проверяем, является ли значение JSON и декодируем его при необходимости
|
||||
if isinstance(value, (str, bytes)) and isinstance(
|
||||
if isinstance(value, str | bytes) and isinstance(
|
||||
self.__table__.columns[column_name].type, JSON
|
||||
):
|
||||
try:
|
||||
|
||||
@@ -21,11 +21,7 @@ from auth.orm import Author
|
||||
from orm.base import BaseModel
|
||||
from orm.shout import Shout
|
||||
from services.db import local_session
|
||||
from services.rbac import (
|
||||
get_permissions_for_role,
|
||||
initialize_community_permissions,
|
||||
user_has_permission,
|
||||
)
|
||||
from auth.rbac_interface import get_rbac_operations
|
||||
|
||||
# Словарь названий ролей
|
||||
role_names = {
|
||||
@@ -59,7 +55,7 @@ class CommunityFollower(BaseModel):
|
||||
__tablename__ = "community_follower"
|
||||
|
||||
community: Mapped[int] = mapped_column(Integer, ForeignKey("community.id"), nullable=False, index=True)
|
||||
follower: Mapped[int] = mapped_column(Integer, ForeignKey(Author.id), nullable=False, index=True)
|
||||
follower: Mapped[int] = mapped_column(Integer, ForeignKey("author.id"), nullable=False, index=True)
|
||||
created_at: Mapped[int] = mapped_column(Integer, nullable=False, default=lambda: int(time.time()))
|
||||
|
||||
# Уникальность по паре сообщество-подписчик
|
||||
@@ -288,7 +284,8 @@ class Community(BaseModel):
|
||||
Инициализирует права ролей для сообщества из дефолтных настроек.
|
||||
Вызывается при создании нового сообщества.
|
||||
"""
|
||||
await initialize_community_permissions(int(self.id))
|
||||
rbac_ops = get_rbac_operations()
|
||||
await rbac_ops.initialize_community_permissions(int(self.id))
|
||||
|
||||
def get_available_roles(self) -> list[str]:
|
||||
"""
|
||||
@@ -399,7 +396,7 @@ class CommunityAuthor(BaseModel):
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
community_id: Mapped[int] = mapped_column(Integer, ForeignKey("community.id"), nullable=False)
|
||||
author_id: Mapped[int] = mapped_column(Integer, ForeignKey(Author.id), nullable=False)
|
||||
author_id: Mapped[int] = mapped_column(Integer, ForeignKey("author.id"), nullable=False)
|
||||
roles: Mapped[str | None] = mapped_column(String, nullable=True, comment="Roles (comma-separated)")
|
||||
joined_at: Mapped[int] = mapped_column(Integer, nullable=False, default=lambda: int(time.time()))
|
||||
|
||||
@@ -478,63 +475,31 @@ class CommunityAuthor(BaseModel):
|
||||
"""
|
||||
|
||||
all_permissions = set()
|
||||
rbac_ops = get_rbac_operations()
|
||||
for role in self.role_list:
|
||||
role_perms = await get_permissions_for_role(role, int(self.community_id))
|
||||
role_perms = await rbac_ops.get_permissions_for_role(role, int(self.community_id))
|
||||
all_permissions.update(role_perms)
|
||||
|
||||
return list(all_permissions)
|
||||
|
||||
def has_permission(
|
||||
self, permission: str | None = None, resource: str | None = None, operation: str | None = None
|
||||
) -> bool:
|
||||
def has_permission(self, permission: str) -> bool:
|
||||
"""
|
||||
Проверяет наличие разрешения у автора
|
||||
Проверяет, есть ли у пользователя указанное право
|
||||
|
||||
Args:
|
||||
permission: Разрешение для проверки (например: "shout:create")
|
||||
resource: Опциональный ресурс (для обратной совместимости)
|
||||
operation: Опциональная операция (для обратной совместимости)
|
||||
permission: Право для проверки (например, "community:create")
|
||||
|
||||
Returns:
|
||||
True если разрешение есть, False если нет
|
||||
True если право есть, False если нет
|
||||
"""
|
||||
# Если передан полный permission, используем его
|
||||
if permission and ":" in permission:
|
||||
# Проверяем права через синхронную функцию
|
||||
try:
|
||||
import asyncio
|
||||
|
||||
from services.rbac import get_permissions_for_role
|
||||
|
||||
all_permissions = set()
|
||||
for role in self.role_list:
|
||||
role_perms = asyncio.run(get_permissions_for_role(role, int(self.community_id)))
|
||||
all_permissions.update(role_perms)
|
||||
|
||||
return permission in all_permissions
|
||||
except Exception:
|
||||
# Fallback: проверяем роли (старый способ)
|
||||
return any(permission == role for role in self.role_list)
|
||||
|
||||
# Если переданы resource и operation, формируем permission
|
||||
if resource and operation:
|
||||
full_permission = f"{resource}:{operation}"
|
||||
try:
|
||||
import asyncio
|
||||
|
||||
from services.rbac import get_permissions_for_role
|
||||
|
||||
all_permissions = set()
|
||||
for role in self.role_list:
|
||||
role_perms = asyncio.run(get_permissions_for_role(role, int(self.community_id)))
|
||||
all_permissions.update(role_perms)
|
||||
|
||||
return full_permission in all_permissions
|
||||
except Exception:
|
||||
# Fallback: проверяем роли (старый способ)
|
||||
return any(full_permission == role for role in self.role_list)
|
||||
|
||||
return False
|
||||
# Проверяем права через синхронную функцию
|
||||
try:
|
||||
# В синхронном контексте не можем использовать await
|
||||
# Используем fallback на проверку ролей
|
||||
return permission in self.role_list
|
||||
except Exception:
|
||||
# FIXME: Fallback: проверяем роли (старый способ)
|
||||
return any(permission == role for role in self.role_list)
|
||||
|
||||
def dict(self, access: bool = False) -> dict[str, Any]:
|
||||
"""
|
||||
@@ -706,7 +671,8 @@ async def check_user_permission_in_community(author_id: int, permission: str, co
|
||||
Returns:
|
||||
True если разрешение есть, False если нет
|
||||
"""
|
||||
return await user_has_permission(author_id, permission, community_id)
|
||||
rbac_ops = get_rbac_operations()
|
||||
return await rbac_ops.user_has_permission(author_id, permission, community_id)
|
||||
|
||||
|
||||
def assign_role_to_user(author_id: int, role: str, community_id: int = 1) -> bool:
|
||||
|
||||
15
orm/draft.py
15
orm/draft.py
@@ -8,6 +8,11 @@ from auth.orm import Author
|
||||
from orm.base import BaseModel as Base
|
||||
from orm.topic import Topic
|
||||
|
||||
# Author уже импортирован в начале файла
|
||||
def get_author_model():
|
||||
"""Возвращает модель Author для использования в запросах"""
|
||||
return Author
|
||||
|
||||
|
||||
class DraftTopic(Base):
|
||||
__tablename__ = "draft_topic"
|
||||
@@ -28,7 +33,7 @@ class DraftAuthor(Base):
|
||||
__tablename__ = "draft_author"
|
||||
|
||||
draft: Mapped[int] = mapped_column(ForeignKey("draft.id"), index=True)
|
||||
author: Mapped[int] = mapped_column(ForeignKey(Author.id), index=True)
|
||||
author: Mapped[int] = mapped_column(ForeignKey("author.id"), index=True)
|
||||
caption: Mapped[str | None] = mapped_column(String, nullable=True, default="")
|
||||
|
||||
__table_args__ = (
|
||||
@@ -44,7 +49,7 @@ class Draft(Base):
|
||||
# required
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
created_at: Mapped[int] = mapped_column(Integer, nullable=False, default=lambda: int(time.time()))
|
||||
created_by: Mapped[int] = mapped_column(ForeignKey(Author.id), nullable=False)
|
||||
created_by: Mapped[int] = mapped_column(ForeignKey("author.id"), nullable=False)
|
||||
community: Mapped[int] = mapped_column(ForeignKey("community.id"), nullable=False, default=1)
|
||||
|
||||
# optional
|
||||
@@ -63,9 +68,9 @@ class Draft(Base):
|
||||
# auto
|
||||
updated_at: Mapped[int | None] = mapped_column(Integer, nullable=True, index=True)
|
||||
deleted_at: Mapped[int | None] = mapped_column(Integer, nullable=True, index=True)
|
||||
updated_by: Mapped[int | None] = mapped_column(ForeignKey(Author.id), nullable=True)
|
||||
deleted_by: Mapped[int | None] = mapped_column(ForeignKey(Author.id), nullable=True)
|
||||
authors = relationship(Author, secondary=DraftAuthor.__table__)
|
||||
updated_by: Mapped[int | None] = mapped_column(ForeignKey("author.id"), nullable=True)
|
||||
deleted_by: Mapped[int | None] = mapped_column(ForeignKey("author.id"), nullable=True)
|
||||
authors = relationship(get_author_model(), secondary=DraftAuthor.__table__)
|
||||
topics = relationship(Topic, secondary=DraftTopic.__table__)
|
||||
|
||||
# shout/publication
|
||||
|
||||
@@ -5,10 +5,16 @@ from typing import Any
|
||||
from sqlalchemy import JSON, DateTime, ForeignKey, Index, Integer, PrimaryKeyConstraint, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
# Импорт Author отложен для избежания циклических импортов
|
||||
from auth.orm import Author
|
||||
from orm.base import BaseModel as Base
|
||||
from utils.logger import root_logger as logger
|
||||
|
||||
# Author уже импортирован в начале файла
|
||||
def get_author_model():
|
||||
"""Возвращает модель Author для использования в запросах"""
|
||||
return Author
|
||||
|
||||
|
||||
class NotificationEntity(Enum):
|
||||
"""
|
||||
@@ -106,7 +112,7 @@ class Notification(Base):
|
||||
status: Mapped[NotificationStatus] = mapped_column(default=NotificationStatus.UNREAD)
|
||||
kind: Mapped[NotificationKind] = mapped_column(nullable=False)
|
||||
|
||||
seen = relationship(Author, secondary="notification_seen")
|
||||
seen = relationship("Author", secondary="notification_seen")
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_notification_created_at", "created_at"),
|
||||
|
||||
@@ -7,6 +7,11 @@ from sqlalchemy.orm import Mapped, mapped_column
|
||||
from auth.orm import Author
|
||||
from orm.base import BaseModel as Base
|
||||
|
||||
# Author уже импортирован в начале файла
|
||||
def get_author_model():
|
||||
"""Возвращает модель Author для использования в запросах"""
|
||||
return Author
|
||||
|
||||
|
||||
class ReactionKind(Enumeration):
|
||||
# TYPE = <reaction index> # rating diff
|
||||
@@ -51,11 +56,11 @@ class Reaction(Base):
|
||||
created_at: Mapped[int] = mapped_column(Integer, nullable=False, default=lambda: int(time.time()), index=True)
|
||||
updated_at: Mapped[int | None] = mapped_column(Integer, nullable=True, comment="Updated at", index=True)
|
||||
deleted_at: Mapped[int | None] = mapped_column(Integer, nullable=True, comment="Deleted at", index=True)
|
||||
deleted_by: Mapped[int | None] = mapped_column(ForeignKey(Author.id), nullable=True)
|
||||
deleted_by: Mapped[int | None] = mapped_column(ForeignKey("author.id"), nullable=True)
|
||||
reply_to: Mapped[int | None] = mapped_column(ForeignKey("reaction.id"), nullable=True)
|
||||
quote: Mapped[str | None] = mapped_column(String, nullable=True, comment="Original quoted text")
|
||||
shout: Mapped[int] = mapped_column(ForeignKey("shout.id"), nullable=False, index=True)
|
||||
created_by: Mapped[int] = mapped_column(ForeignKey(Author.id), nullable=False)
|
||||
created_by: Mapped[int] = mapped_column(ForeignKey("author.id"), nullable=False)
|
||||
kind: Mapped[str] = mapped_column(String, nullable=False, index=True)
|
||||
|
||||
oid: Mapped[str | None] = mapped_column(String)
|
||||
|
||||
22
orm/shout.py
22
orm/shout.py
@@ -4,11 +4,17 @@ from typing import Any
|
||||
from sqlalchemy import JSON, Boolean, ForeignKey, Index, Integer, PrimaryKeyConstraint, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
# Импорт Author отложен для избежания циклических импортов
|
||||
from auth.orm import Author
|
||||
from orm.base import BaseModel as Base
|
||||
from orm.reaction import Reaction
|
||||
from orm.topic import Topic
|
||||
|
||||
# Author уже импортирован в начале файла
|
||||
def get_author_model():
|
||||
"""Возвращает модель Author для использования в запросах"""
|
||||
return Author
|
||||
|
||||
|
||||
class ShoutTopic(Base):
|
||||
"""
|
||||
@@ -37,7 +43,7 @@ class ShoutTopic(Base):
|
||||
class ShoutReactionsFollower(Base):
|
||||
__tablename__ = "shout_reactions_followers"
|
||||
|
||||
follower: Mapped[int] = mapped_column(ForeignKey(Author.id), index=True)
|
||||
follower: Mapped[int] = mapped_column(ForeignKey("author.id"), index=True)
|
||||
shout: Mapped[int] = mapped_column(ForeignKey("shout.id"), index=True)
|
||||
auto: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
created_at: Mapped[int] = mapped_column(Integer, nullable=False, default=lambda: int(time.time()))
|
||||
@@ -64,7 +70,7 @@ class ShoutAuthor(Base):
|
||||
__tablename__ = "shout_author"
|
||||
|
||||
shout: Mapped[int] = mapped_column(ForeignKey("shout.id"), index=True)
|
||||
author: Mapped[int] = mapped_column(ForeignKey(Author.id), index=True)
|
||||
author: Mapped[int] = mapped_column(ForeignKey("author.id"), index=True)
|
||||
caption: Mapped[str | None] = mapped_column(String, nullable=True, default="")
|
||||
|
||||
# Определяем дополнительные индексы
|
||||
@@ -89,9 +95,9 @@ class Shout(Base):
|
||||
featured_at: Mapped[int | None] = mapped_column(Integer, nullable=True, index=True)
|
||||
deleted_at: Mapped[int | None] = mapped_column(Integer, nullable=True, index=True)
|
||||
|
||||
created_by: Mapped[int] = mapped_column(ForeignKey(Author.id), nullable=False)
|
||||
updated_by: Mapped[int | None] = mapped_column(ForeignKey(Author.id), nullable=True)
|
||||
deleted_by: Mapped[int | None] = mapped_column(ForeignKey(Author.id), nullable=True)
|
||||
created_by: Mapped[int] = mapped_column(ForeignKey("author.id"), nullable=False)
|
||||
updated_by: Mapped[int | None] = mapped_column(ForeignKey("author.id"), nullable=True)
|
||||
deleted_by: Mapped[int | None] = mapped_column(ForeignKey("author.id"), nullable=True)
|
||||
community: Mapped[int] = mapped_column(ForeignKey("community.id"), nullable=False)
|
||||
|
||||
body: Mapped[str] = mapped_column(String, nullable=False, comment="Body")
|
||||
@@ -104,9 +110,9 @@ class Shout(Base):
|
||||
layout: Mapped[str] = mapped_column(String, nullable=False, default="article")
|
||||
media: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True)
|
||||
|
||||
authors = relationship(Author, secondary="shout_author")
|
||||
topics = relationship(Topic, secondary="shout_topic")
|
||||
reactions = relationship(Reaction)
|
||||
authors = relationship("Author", secondary="shout_author")
|
||||
topics = relationship("Topic", secondary="shout_topic")
|
||||
reactions = relationship("Reaction")
|
||||
|
||||
lang: Mapped[str] = mapped_column(String, nullable=False, default="ru", comment="Language")
|
||||
version_of: Mapped[int | None] = mapped_column(ForeignKey("shout.id"), nullable=True)
|
||||
|
||||
@@ -14,6 +14,11 @@ from sqlalchemy.orm import Mapped, mapped_column
|
||||
from auth.orm import Author
|
||||
from orm.base import BaseModel as Base
|
||||
|
||||
# Author уже импортирован в начале файла
|
||||
def get_author_model():
|
||||
"""Возвращает модель Author для использования в запросах"""
|
||||
return Author
|
||||
|
||||
|
||||
class TopicFollower(Base):
|
||||
"""
|
||||
@@ -28,7 +33,7 @@ class TopicFollower(Base):
|
||||
|
||||
__tablename__ = "topic_followers"
|
||||
|
||||
follower: Mapped[int] = mapped_column(ForeignKey(Author.id))
|
||||
follower: Mapped[int] = mapped_column(ForeignKey("author.id"))
|
||||
topic: Mapped[int] = mapped_column(ForeignKey("topic.id"))
|
||||
created_at: Mapped[int] = mapped_column(Integer, nullable=False, default=lambda: int(time.time()))
|
||||
auto: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
|
||||
Reference in New Issue
Block a user