This commit is contained in:
@@ -62,7 +62,6 @@ repos:
|
||||
# additional_dependencies: [
|
||||
# "types-redis",
|
||||
# "types-requests",
|
||||
# "types-passlib",
|
||||
# "types-Authlib",
|
||||
# "sqlalchemy[mypy]"
|
||||
# ]
|
||||
|
48
CHANGELOG.md
48
CHANGELOG.md
@@ -1,5 +1,32 @@
|
||||
# Changelog
|
||||
|
||||
## [0.7.9] - 2025-07-24
|
||||
|
||||
### 🔐 Улучшения системы ролей и авторизации
|
||||
|
||||
#### Исправления в управлении ролями
|
||||
- **Корректная работа CommunityAuthor**: Исправлена логика сохранения и получения ролей пользователей
|
||||
- **Автоматическое назначение ролей**: При создании пользователя теперь гарантированно назначаются роли `reader` и `author`
|
||||
- **Нормализация email**: Email приводится к нижнему регистру при создании и обновлении пользователя
|
||||
- **Обработка уникальности email**: Предотвращено создание дублей пользователей с одинаковым email
|
||||
|
||||
|
||||
### 🔧 Улучшения тестирования
|
||||
- **Инициализация сообщества**: Добавлена инициализация прав сообщества в фикстуре
|
||||
- **Область видимости**: Изменена область видимости фикстуры на function для изоляции тестов
|
||||
- **Настройки ролей**: Расширен список доступных ролей
|
||||
- **Расширенные тесты RBAC**: Добавлены comprehensive тесты для проверки ролей и создания пользователей
|
||||
- **Улучшенная диагностика**: Расширено логирование для облегчения отладки
|
||||
|
||||
#### Оптимизации
|
||||
- **Производительность**: Оптимизированы запросы к базе данных при работе с ролями
|
||||
- **Безопасность**: Усилена проверка целостности данных при создании и обновлении пользователей
|
||||
|
||||
### 🛠 Технические улучшения
|
||||
- Рефакторинг методов `create_user()` и `update_user()`
|
||||
- Исправлены потенциальные утечки данных
|
||||
- Улучшена обработка краевых случаев в системе авторизации
|
||||
|
||||
## [0.7.8] - 2025-07-04
|
||||
|
||||
### 💬 Система управления реакциями в админ-панели
|
||||
@@ -1801,3 +1828,24 @@ Radical architecture simplification with separation into service layer and thin
|
||||
- `settings` moved to base and now smaller
|
||||
- new outside auth schema
|
||||
- removed `gittask`, `auth`, `inbox`, `migration`
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Migration
|
||||
- Подготовка к миграции на SQLAlchemy 2.0
|
||||
- Обновлена базовая модель для совместимости с новой версией ORM
|
||||
- Улучшена типизация и обработка метаданных моделей
|
||||
- Добавлена поддержка `DeclarativeBase`
|
||||
|
||||
### Improvements
|
||||
- Более надежное преобразование типов в ORM моделях
|
||||
- Расширена функциональность базового класса моделей
|
||||
- Улучшена обработка JSON-полей при сериализации
|
||||
|
||||
### Fixed
|
||||
- Исправлены потенциальные проблемы с типизацией в ORM
|
||||
- Оптимизирована работа с метаданными SQLAlchemy
|
||||
|
||||
### Changed
|
||||
- Обновлен подход к работе с ORM-моделями
|
||||
- Рефакторинг базового класса моделей для соответствия современным практикам SQLAlchemy
|
||||
|
@@ -4,7 +4,7 @@ from sqlalchemy import engine_from_config, pool
|
||||
|
||||
# Импорт всех моделей для корректной генерации миграций
|
||||
from alembic import context
|
||||
from services.db import Base
|
||||
from orm.base import BaseModel as Base
|
||||
from settings import DB_URL
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
|
@@ -2,7 +2,7 @@ from binascii import hexlify
|
||||
from hashlib import sha256
|
||||
from typing import TYPE_CHECKING, Any, TypeVar
|
||||
|
||||
from passlib.hash import bcrypt
|
||||
import bcrypt
|
||||
|
||||
from auth.exceptions import ExpiredToken, InvalidPassword, InvalidToken
|
||||
from auth.jwtcodec import JWTCodec
|
||||
@@ -39,7 +39,8 @@ class Password:
|
||||
str: Закодированный пароль
|
||||
"""
|
||||
password_sha256 = Password._get_sha256(password)
|
||||
return bcrypt.using(rounds=10).hash(password_sha256)
|
||||
salt = bcrypt.gensalt(rounds=10)
|
||||
return bcrypt.hashpw(password_sha256, salt).decode("utf-8")
|
||||
|
||||
@staticmethod
|
||||
def verify(password: str, hashed: str) -> bool:
|
||||
@@ -61,7 +62,7 @@ class Password:
|
||||
hashed_bytes = Password._to_bytes(hashed)
|
||||
password_sha256 = Password._get_sha256(password)
|
||||
|
||||
return bcrypt.verify(password_sha256, hashed_bytes)
|
||||
return bcrypt.checkpw(password_sha256, hashed_bytes) # Изменил verify на checkpw
|
||||
|
||||
|
||||
class Identity:
|
||||
|
@@ -586,22 +586,7 @@ def _create_new_oauth_user(provider: str, profile: dict, email: str, session: An
|
||||
# Получаем сообщество для назначения дефолтных ролей
|
||||
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"Не удалось инициализировать права сообщества {target_community_id}: {e}")
|
||||
|
||||
# Получаем дефолтные роли сообщества или используем стандартные
|
||||
try:
|
||||
default_roles = community.get_default_roles()
|
||||
if not default_roles:
|
||||
default_roles = ["reader", "author"]
|
||||
except AttributeError:
|
||||
default_roles = ["reader", "author"]
|
||||
|
||||
# Создаем CommunityAuthor с дефолтными ролями
|
||||
community_author = CommunityAuthor(
|
||||
|
100
orm/base.py
Normal file
100
orm/base.py
Normal file
@@ -0,0 +1,100 @@
|
||||
import builtins
|
||||
import logging
|
||||
from typing import Any, Callable, ClassVar, Type, Union
|
||||
|
||||
import orjson
|
||||
from sqlalchemy import JSON, Column, Integer
|
||||
from sqlalchemy.orm import declarative_base, declared_attr
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Глобальный реестр моделей
|
||||
REGISTRY: dict[str, Type[Any]] = {}
|
||||
|
||||
# Список полей для фильтрации при сериализации
|
||||
FILTERED_FIELDS: list[str] = []
|
||||
|
||||
# Создаем базовый класс для декларативных моделей
|
||||
_Base = declarative_base()
|
||||
|
||||
|
||||
class SafeColumnMixin:
|
||||
"""
|
||||
Миксин для безопасного присваивания значений столбцам с автоматическим преобразованием типов
|
||||
"""
|
||||
|
||||
@declared_attr
|
||||
def __safe_setattr__(self) -> Callable:
|
||||
def safe_setattr(self, key: str, value: Any) -> None:
|
||||
"""
|
||||
Безопасно устанавливает атрибут для экземпляра.
|
||||
|
||||
Args:
|
||||
key (str): Имя атрибута.
|
||||
value (Any): Значение атрибута.
|
||||
"""
|
||||
setattr(self, key, value)
|
||||
|
||||
return safe_setattr
|
||||
|
||||
def __setattr__(self, key: str, value: Any) -> None:
|
||||
"""
|
||||
Переопределяем __setattr__ для использования безопасного присваивания
|
||||
"""
|
||||
safe_method = getattr(self, "__safe_setattr__", object.__setattr__)
|
||||
safe_method(self, key, value)
|
||||
|
||||
|
||||
class BaseModel(_Base, SafeColumnMixin): # 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 поля в словари.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Словарь с атрибутами объекта
|
||||
"""
|
||||
column_names = filter(lambda x: x not in FILTERED_FIELDS, self.__table__.columns.keys())
|
||||
data: builtins.dict[str, Any] = {}
|
||||
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}")
|
||||
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)
|
@@ -3,7 +3,7 @@ import time
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from services.db import BaseModel as Base
|
||||
from orm.base import BaseModel as Base
|
||||
|
||||
|
||||
class ShoutCollection(Base):
|
||||
|
@@ -1,12 +1,12 @@
|
||||
import time
|
||||
from typing import Any, Dict
|
||||
|
||||
from sqlalchemy import JSON, Boolean, Column, ForeignKey, Index, Integer, String, Text, UniqueConstraint, distinct, func
|
||||
from sqlalchemy import JSON, Boolean, Column, ForeignKey, Index, Integer, String, UniqueConstraint, distinct, func
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from auth.orm import Author
|
||||
from services.db import BaseModel
|
||||
from orm.base import BaseModel
|
||||
from services.rbac import get_permissions_for_role
|
||||
|
||||
# Словарь названий ролей
|
||||
@@ -372,7 +372,7 @@ class CommunityAuthor(BaseModel):
|
||||
id = Column(Integer, primary_key=True)
|
||||
community_id = Column(Integer, ForeignKey("community.id"), nullable=False)
|
||||
author_id = Column(Integer, ForeignKey("author.id"), nullable=False)
|
||||
roles = Column(Text, nullable=True, comment="Roles (comma-separated)")
|
||||
roles = Column(String, nullable=True, comment="Roles (comma-separated)")
|
||||
joined_at = Column(Integer, nullable=False, default=lambda: int(time.time()))
|
||||
|
||||
# Связи
|
||||
@@ -435,12 +435,16 @@ class CommunityAuthor(BaseModel):
|
||||
|
||||
def set_roles(self, roles: list[str]) -> None:
|
||||
"""
|
||||
Устанавливает полный список ролей (заменяет текущие)
|
||||
Устанавливает роли для CommunityAuthor.
|
||||
|
||||
Args:
|
||||
roles: Список ролей для установки
|
||||
"""
|
||||
self.role_list = roles
|
||||
# Фильтруем и очищаем роли
|
||||
valid_roles = [role.strip() for role in roles if role and role.strip()]
|
||||
|
||||
# Если список пустой, устанавливаем None
|
||||
self.roles = ",".join(valid_roles) if valid_roles else ""
|
||||
|
||||
async def get_permissions(self) -> list[str]:
|
||||
"""
|
||||
|
@@ -4,14 +4,14 @@ from sqlalchemy import JSON, Boolean, Column, ForeignKey, Integer, String
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from auth.orm import Author
|
||||
from orm.base import BaseModel as Base
|
||||
from orm.topic import Topic
|
||||
from services.db import BaseModel as Base
|
||||
|
||||
|
||||
class DraftTopic(Base):
|
||||
__tablename__ = "draft_topic"
|
||||
|
||||
id = None # type: ignore
|
||||
id = None # type: ignore[misc]
|
||||
shout = Column(ForeignKey("draft.id"), primary_key=True, index=True)
|
||||
topic = Column(ForeignKey("topic.id"), primary_key=True, index=True)
|
||||
main = Column(Boolean, nullable=True)
|
||||
@@ -20,7 +20,7 @@ class DraftTopic(Base):
|
||||
class DraftAuthor(Base):
|
||||
__tablename__ = "draft_author"
|
||||
|
||||
id = None # type: ignore
|
||||
id = None # type: ignore[misc]
|
||||
shout = Column(ForeignKey("draft.id"), primary_key=True, index=True)
|
||||
author = Column(ForeignKey("author.id"), primary_key=True, index=True)
|
||||
caption = Column(String, nullable=True, default="")
|
||||
|
@@ -3,7 +3,7 @@ import enum
|
||||
from sqlalchemy import Column, ForeignKey, String
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from services.db import BaseModel as Base
|
||||
from orm.base import BaseModel as Base
|
||||
|
||||
|
||||
class InviteStatus(enum.Enum):
|
||||
@@ -12,7 +12,7 @@ class InviteStatus(enum.Enum):
|
||||
REJECTED = "REJECTED"
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, value):
|
||||
def from_string(cls, value: str) -> "Invite":
|
||||
return cls(value)
|
||||
|
||||
|
||||
|
@@ -1,35 +1,120 @@
|
||||
import enum
|
||||
import time
|
||||
from datetime import datetime
|
||||
from enum import Enum, auto
|
||||
|
||||
from sqlalchemy import JSON, Column, ForeignKey, Integer, String
|
||||
from sqlalchemy import JSON, Column, DateTime, ForeignKey, Integer, String
|
||||
from sqlalchemy import Enum as SQLAlchemyEnum
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from auth.orm import Author
|
||||
from services.db import BaseModel as Base
|
||||
from orm.author import Author
|
||||
from orm.base import BaseModel as Base
|
||||
from services.logger import root_logger as logger
|
||||
|
||||
|
||||
class NotificationStatus(Enum):
|
||||
"""Статусы уведомлений."""
|
||||
|
||||
UNREAD = auto()
|
||||
READ = auto()
|
||||
ARCHIVED = auto()
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, value: str) -> "NotificationStatus":
|
||||
"""
|
||||
Создает экземпляр статуса уведомления из строки.
|
||||
|
||||
Args:
|
||||
value (str): Строковое представление статуса.
|
||||
|
||||
Returns:
|
||||
NotificationStatus: Экземпляр статуса уведомления.
|
||||
"""
|
||||
try:
|
||||
return cls[value.upper()]
|
||||
except KeyError:
|
||||
logger.error(f"Неверный статус уведомления: {value}")
|
||||
raise ValueError("Неверный статус уведомления") # noqa: B904
|
||||
|
||||
|
||||
class NotificationKind(Enum):
|
||||
"""Типы уведомлений."""
|
||||
|
||||
COMMENT = auto()
|
||||
MENTION = auto()
|
||||
REACTION = auto()
|
||||
FOLLOW = auto()
|
||||
INVITE = auto()
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, value: str) -> "NotificationKind":
|
||||
"""
|
||||
Создает экземпляр типа уведомления из строки.
|
||||
|
||||
Args:
|
||||
value (str): Строковое представление типа.
|
||||
|
||||
Returns:
|
||||
NotificationKind: Экземпляр типа уведомления.
|
||||
"""
|
||||
try:
|
||||
return cls[value.upper()]
|
||||
except KeyError:
|
||||
logger.error(f"Неверный тип уведомления: {value}")
|
||||
raise ValueError("Неверный тип уведомления") # noqa: B904
|
||||
|
||||
|
||||
class NotificationEntity(enum.Enum):
|
||||
REACTION = "reaction"
|
||||
"""Сущности, связанные с уведомлениями."""
|
||||
|
||||
TOPIC = "topic"
|
||||
COMMENT = "comment"
|
||||
SHOUT = "shout"
|
||||
FOLLOWER = "follower"
|
||||
AUTHOR = "author"
|
||||
COMMUNITY = "community"
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, value):
|
||||
def from_string(cls, value: str) -> "NotificationEntity":
|
||||
"""
|
||||
Создает экземпляр сущности уведомления из строки.
|
||||
|
||||
Args:
|
||||
value (str): Строковое представление сущности.
|
||||
|
||||
Returns:
|
||||
NotificationEntity: Экземпляр сущности уведомления.
|
||||
"""
|
||||
try:
|
||||
return cls(value)
|
||||
except ValueError:
|
||||
logger.error(f"Неверная сущность уведомления: {value}")
|
||||
raise ValueError("Неверная сущность уведомления") # noqa: B904
|
||||
|
||||
|
||||
class NotificationAction(enum.Enum):
|
||||
"""Действия в уведомлениях."""
|
||||
|
||||
CREATE = "create"
|
||||
UPDATE = "update"
|
||||
DELETE = "delete"
|
||||
SEEN = "seen"
|
||||
FOLLOW = "follow"
|
||||
UNFOLLOW = "unfollow"
|
||||
MENTION = "mention"
|
||||
REACT = "react"
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, value):
|
||||
def from_string(cls, value: str) -> "NotificationAction":
|
||||
"""
|
||||
Создает экземпляр действия уведомления из строки.
|
||||
|
||||
Args:
|
||||
value (str): Строковое представление действия.
|
||||
|
||||
Returns:
|
||||
NotificationAction: Экземпляр действия уведомления.
|
||||
"""
|
||||
try:
|
||||
return cls(value)
|
||||
except ValueError:
|
||||
logger.error(f"Неверное действие уведомления: {value}")
|
||||
raise ValueError("Неверное действие уведомления") # noqa: B904
|
||||
|
||||
|
||||
class NotificationSeen(Base):
|
||||
@@ -42,22 +127,31 @@ class NotificationSeen(Base):
|
||||
class Notification(Base):
|
||||
__tablename__ = "notification"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
created_at = Column(Integer, server_default=str(int(time.time())))
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
entity = Column(String, nullable=False)
|
||||
action = Column(String, nullable=False)
|
||||
payload = Column(JSON, nullable=True)
|
||||
|
||||
status = Column(SQLAlchemyEnum(NotificationStatus), default=NotificationStatus.UNREAD)
|
||||
kind = Column(SQLAlchemyEnum(NotificationKind), nullable=False)
|
||||
|
||||
seen = relationship(Author, secondary="notification_seen")
|
||||
|
||||
def set_entity(self, entity: NotificationEntity):
|
||||
self.entity = entity.value # type: ignore[assignment]
|
||||
"""Устанавливает сущность уведомления."""
|
||||
self.entity = entity.value
|
||||
|
||||
def get_entity(self) -> NotificationEntity:
|
||||
"""Возвращает сущность уведомления."""
|
||||
return NotificationEntity.from_string(self.entity)
|
||||
|
||||
def set_action(self, action: NotificationAction):
|
||||
self.action = action.value # type: ignore[assignment]
|
||||
"""Устанавливает действие уведомления."""
|
||||
self.action = action.value
|
||||
|
||||
def get_action(self) -> NotificationAction:
|
||||
"""Возвращает действие уведомления."""
|
||||
return NotificationAction.from_string(self.action)
|
||||
|
@@ -3,7 +3,7 @@ from enum import Enum as Enumeration
|
||||
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String
|
||||
|
||||
from services.db import BaseModel as Base
|
||||
from orm.base import BaseModel as Base
|
||||
|
||||
|
||||
class ReactionKind(Enumeration):
|
||||
|
@@ -4,9 +4,9 @@ from sqlalchemy import JSON, Boolean, Column, ForeignKey, Index, Integer, String
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from auth.orm import Author
|
||||
from orm.base import BaseModel as Base
|
||||
from orm.reaction import Reaction
|
||||
from orm.topic import Topic
|
||||
from services.db import BaseModel as Base
|
||||
|
||||
|
||||
class ShoutTopic(Base):
|
||||
@@ -21,7 +21,7 @@ class ShoutTopic(Base):
|
||||
|
||||
__tablename__ = "shout_topic"
|
||||
|
||||
id = None # type: ignore
|
||||
id = None # type: ignore[misc]
|
||||
shout = Column(ForeignKey("shout.id"), primary_key=True, index=True)
|
||||
topic = Column(ForeignKey("topic.id"), primary_key=True, index=True)
|
||||
main = Column(Boolean, nullable=True)
|
||||
@@ -36,7 +36,7 @@ class ShoutTopic(Base):
|
||||
class ShoutReactionsFollower(Base):
|
||||
__tablename__ = "shout_reactions_followers"
|
||||
|
||||
id = None # type: ignore
|
||||
id = None # type: ignore[misc]
|
||||
follower = Column(ForeignKey("author.id"), primary_key=True, index=True)
|
||||
shout = Column(ForeignKey("shout.id"), primary_key=True, index=True)
|
||||
auto = Column(Boolean, nullable=False, default=False)
|
||||
@@ -56,7 +56,7 @@ class ShoutAuthor(Base):
|
||||
|
||||
__tablename__ = "shout_author"
|
||||
|
||||
id = None # type: ignore
|
||||
id = None # type: ignore[misc]
|
||||
shout = Column(ForeignKey("shout.id"), primary_key=True, index=True)
|
||||
author = Column(ForeignKey("author.id"), primary_key=True, index=True)
|
||||
caption = Column(String, nullable=True, default="")
|
||||
|
@@ -2,7 +2,7 @@ import time
|
||||
|
||||
from sqlalchemy import JSON, Boolean, Column, ForeignKey, Index, Integer, String
|
||||
|
||||
from services.db import BaseModel as Base
|
||||
from orm.base import BaseModel as Base
|
||||
|
||||
|
||||
class TopicFollower(Base):
|
||||
@@ -18,7 +18,7 @@ class TopicFollower(Base):
|
||||
|
||||
__tablename__ = "topic_followers"
|
||||
|
||||
id = None # type: ignore
|
||||
id = None # type: ignore[misc]
|
||||
follower = Column(Integer, ForeignKey("author.id"), primary_key=True)
|
||||
topic = Column(Integer, ForeignKey("topic.id"), primary_key=True)
|
||||
created_at = Column(Integer, nullable=False, default=int(time.time()))
|
||||
|
482
package-lock.json
generated
482
package-lock.json
generated
@@ -11,22 +11,22 @@
|
||||
"@solidjs/router": "^0.15.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.0.6",
|
||||
"@biomejs/biome": "^2.1.2",
|
||||
"@graphql-codegen/cli": "^5.0.7",
|
||||
"@graphql-codegen/client-preset": "^4.8.3",
|
||||
"@graphql-codegen/typescript": "^4.0.6",
|
||||
"@graphql-codegen/typescript-operations": "^4.2.0",
|
||||
"@graphql-codegen/typescript-resolvers": "^4.0.6",
|
||||
"@types/node": "^24.0.7",
|
||||
"@graphql-codegen/typescript": "^4.1.6",
|
||||
"@graphql-codegen/typescript-operations": "^4.6.1",
|
||||
"@graphql-codegen/typescript-resolvers": "^4.5.1",
|
||||
"@types/node": "^24.1.0",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"graphql": "^16.11.0",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"lightningcss": "^1.30.0",
|
||||
"lightningcss": "^1.30.1",
|
||||
"prismjs": "^1.30.0",
|
||||
"solid-js": "^1.9.7",
|
||||
"terser": "^5.39.0",
|
||||
"terser": "^5.43.0",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^7.0.0",
|
||||
"vite": "^7.0.6",
|
||||
"vite-plugin-solid": "^2.11.7"
|
||||
}
|
||||
},
|
||||
@@ -242,14 +242,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.27.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz",
|
||||
"integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==",
|
||||
"version": "7.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz",
|
||||
"integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.27.2",
|
||||
"@babel/types": "^7.27.6"
|
||||
"@babel/types": "^7.28.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -304,9 +304,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.27.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
|
||||
"integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==",
|
||||
"version": "7.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz",
|
||||
"integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -348,9 +348,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz",
|
||||
"integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==",
|
||||
"version": "7.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
|
||||
"integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -569,9 +569,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.6.tgz",
|
||||
"integrity": "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
|
||||
"integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -586,9 +586,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.6.tgz",
|
||||
"integrity": "sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz",
|
||||
"integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -603,9 +603,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.6.tgz",
|
||||
"integrity": "sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -620,9 +620,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.6.tgz",
|
||||
"integrity": "sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -637,9 +637,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.6.tgz",
|
||||
"integrity": "sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -654,9 +654,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.6.tgz",
|
||||
"integrity": "sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -671,9 +671,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.6.tgz",
|
||||
"integrity": "sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -688,9 +688,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.6.tgz",
|
||||
"integrity": "sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -705,9 +705,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.6.tgz",
|
||||
"integrity": "sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz",
|
||||
"integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -722,9 +722,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.6.tgz",
|
||||
"integrity": "sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -739,9 +739,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.6.tgz",
|
||||
"integrity": "sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz",
|
||||
"integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -756,9 +756,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.6.tgz",
|
||||
"integrity": "sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz",
|
||||
"integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -773,9 +773,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.6.tgz",
|
||||
"integrity": "sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz",
|
||||
"integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
@@ -790,9 +790,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.6.tgz",
|
||||
"integrity": "sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz",
|
||||
"integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -807,9 +807,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.6.tgz",
|
||||
"integrity": "sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz",
|
||||
"integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -824,9 +824,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.6.tgz",
|
||||
"integrity": "sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz",
|
||||
"integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -841,9 +841,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.6.tgz",
|
||||
"integrity": "sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -858,9 +858,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.6.tgz",
|
||||
"integrity": "sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -875,9 +875,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.6.tgz",
|
||||
"integrity": "sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -892,9 +892,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.6.tgz",
|
||||
"integrity": "sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -909,9 +909,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.6.tgz",
|
||||
"integrity": "sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -926,9 +926,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.6.tgz",
|
||||
"integrity": "sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -943,9 +943,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.6.tgz",
|
||||
"integrity": "sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -960,9 +960,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.6.tgz",
|
||||
"integrity": "sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -977,9 +977,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.6.tgz",
|
||||
"integrity": "sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz",
|
||||
"integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -994,9 +994,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.6.tgz",
|
||||
"integrity": "sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1405,13 +1405,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/apollo-engine-loader": {
|
||||
"version": "8.0.21",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/apollo-engine-loader/-/apollo-engine-loader-8.0.21.tgz",
|
||||
"integrity": "sha512-3o63uKvx2d/01GhR8Q4RACIScJG7SxliU+xxPVaC6SWpsRkvfHKXJITWctNIw5PBH5HiB25sL9a5AFHCQp0OEQ==",
|
||||
"version": "8.0.22",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/apollo-engine-loader/-/apollo-engine-loader-8.0.22.tgz",
|
||||
"integrity": "sha512-ssD2wNxeOTRcUEkuGcp0KfZAGstL9YLTe/y3erTDZtOs2wL1TJESw8NVAp+3oUHPeHKBZQB4Z6RFEbPgMdT2wA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/utils": "^10.9.0",
|
||||
"@graphql-tools/utils": "^10.9.1",
|
||||
"@whatwg-node/fetch": "^0.10.0",
|
||||
"sync-fetch": "0.6.0-2",
|
||||
"tslib": "^2.4.0"
|
||||
@@ -1424,13 +1424,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/batch-execute": {
|
||||
"version": "9.0.17",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/batch-execute/-/batch-execute-9.0.17.tgz",
|
||||
"integrity": "sha512-i7BqBkUP2+ex8zrQrCQTEt6nYHQmIey9qg7CMRRa1hXCY2X8ZCVjxsvbsi7gOLwyI/R3NHxSRDxmzZevE2cPLg==",
|
||||
"version": "9.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/batch-execute/-/batch-execute-9.0.18.tgz",
|
||||
"integrity": "sha512-KtBglqPGR/3CZtQevFRBBc6MJpIgxBqfCrUV5sdC3oJsafmPShgr+lxM178SW5i1QHmiVAScOWGWqWp9HbnpoQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/utils": "^10.8.1",
|
||||
"@graphql-tools/utils": "^10.9.0",
|
||||
"@whatwg-node/promise-helpers": "^1.3.0",
|
||||
"dataloader": "^2.2.3",
|
||||
"tslib": "^2.8.1"
|
||||
@@ -1443,14 +1443,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/code-file-loader": {
|
||||
"version": "8.1.21",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/code-file-loader/-/code-file-loader-8.1.21.tgz",
|
||||
"integrity": "sha512-NmHEijQ9uEPcM5riM3NsQcT2piESgV2QX6/pIcKineBXQ/2nbeKtxOqWi2omCVLHSKmjOlR1Yyn3E2alqWVOxg==",
|
||||
"version": "8.1.22",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/code-file-loader/-/code-file-loader-8.1.22.tgz",
|
||||
"integrity": "sha512-FSka29kqFkfFmw36CwoQ+4iyhchxfEzPbXOi37lCEjWLHudGaPkXc3RyB9LdmBxx3g3GHEu43a5n5W8gfcrMdA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/graphql-tag-pluck": "8.3.20",
|
||||
"@graphql-tools/utils": "^10.9.0",
|
||||
"@graphql-tools/graphql-tag-pluck": "8.3.21",
|
||||
"@graphql-tools/utils": "^10.9.1",
|
||||
"globby": "^11.0.3",
|
||||
"tslib": "^2.4.0",
|
||||
"unixify": "^1.0.0"
|
||||
@@ -1463,16 +1463,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/delegate": {
|
||||
"version": "10.2.21",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-10.2.21.tgz",
|
||||
"integrity": "sha512-YLyyuhxrZniVufZV/6Oba5xIvWqVRyZrO8LsM+hI4Q6/aR1OdJafi9IBqCE2hUDPfIc8wkhqixA2/WT+oApY3g==",
|
||||
"version": "10.2.22",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-10.2.22.tgz",
|
||||
"integrity": "sha512-1jkTF5DIhO1YJ0dlgY03DZYAiSwlu5D2mdjeq+f6oyflyKG9E4SPmkLgVdDSNSfGxFHHrjIvYjUhPYV0vAOiDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/batch-execute": "^9.0.17",
|
||||
"@graphql-tools/executor": "^1.4.7",
|
||||
"@graphql-tools/schema": "^10.0.11",
|
||||
"@graphql-tools/utils": "^10.8.1",
|
||||
"@graphql-tools/batch-execute": "^9.0.18",
|
||||
"@graphql-tools/executor": "^1.4.8",
|
||||
"@graphql-tools/schema": "^10.0.24",
|
||||
"@graphql-tools/utils": "^10.9.0",
|
||||
"@repeaterjs/repeater": "^3.0.6",
|
||||
"@whatwg-node/promise-helpers": "^1.3.0",
|
||||
"dataloader": "^2.2.3",
|
||||
@@ -1504,13 +1504,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/executor": {
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/executor/-/executor-1.4.8.tgz",
|
||||
"integrity": "sha512-eMFWo30+L8BPME5qhJ3b4WOEAMSIMdi41F0afp40RH9RWQWnJ9R9Tr6vq7CZzmlM8qxymEE4UMAnu2qG/5Jyqg==",
|
||||
"version": "1.4.9",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/executor/-/executor-1.4.9.tgz",
|
||||
"integrity": "sha512-SAUlDT70JAvXeqV87gGzvDzUGofn39nvaVcVhNf12Dt+GfWHtNNO/RCn/Ea4VJaSLGzraUd41ObnN3i80EBU7w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/utils": "^10.9.0",
|
||||
"@graphql-tools/utils": "^10.9.1",
|
||||
"@graphql-typed-document-node/core": "^3.2.0",
|
||||
"@repeaterjs/repeater": "^3.0.4",
|
||||
"@whatwg-node/disposablestack": "^0.0.6",
|
||||
@@ -1542,19 +1542,36 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/executor-graphql-ws": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/executor-graphql-ws/-/executor-graphql-ws-2.0.5.tgz",
|
||||
"integrity": "sha512-gI/D9VUzI1Jt1G28GYpvm5ckupgJ5O8mi5Y657UyuUozX34ErfVdZ81g6oVcKFQZ60LhCzk7jJeykK48gaLhDw==",
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/executor-graphql-ws/-/executor-graphql-ws-2.0.6.tgz",
|
||||
"integrity": "sha512-hLmY+h1HDM4+y4EXP0SgNFd6hXEs4LCMAxvvdfPAwrzHNM04B0wnlcOi8Rze3e7AA9edxXQsm3UN4BE04U2OMg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/executor-common": "^0.0.4",
|
||||
"@graphql-tools/utils": "^10.8.1",
|
||||
"@graphql-tools/executor-common": "^0.0.5",
|
||||
"@graphql-tools/utils": "^10.9.0",
|
||||
"@whatwg-node/disposablestack": "^0.0.6",
|
||||
"graphql-ws": "^6.0.3",
|
||||
"graphql-ws": "^6.0.6",
|
||||
"isomorphic-ws": "^5.0.0",
|
||||
"tslib": "^2.8.1",
|
||||
"ws": "^8.17.1"
|
||||
"ws": "^8.18.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/executor-graphql-ws/node_modules/@graphql-tools/executor-common": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/executor-common/-/executor-common-0.0.5.tgz",
|
||||
"integrity": "sha512-DBTQDGYajhUd4iBZ/yYc1LY85QTVhgTpGPCFT5iz0CPObgye0smsE5nd/BIcdbML7SXv2wFvQhVA3mCJJ32WuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@envelop/core": "^5.3.0",
|
||||
"@graphql-tools/utils": "^10.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
@@ -1588,13 +1605,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/executor-legacy-ws": {
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/executor-legacy-ws/-/executor-legacy-ws-1.1.18.tgz",
|
||||
"integrity": "sha512-KCsf4e3t/TyT06GeXEbWW08tbN+/uYOhFDU7RRMP4S1zIVIsIcdFmCjemBtrYDu93mwib63NidGX+mQXm1tmLg==",
|
||||
"version": "1.1.19",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/executor-legacy-ws/-/executor-legacy-ws-1.1.19.tgz",
|
||||
"integrity": "sha512-bEbv/SlEdhWQD0WZLUX1kOenEdVZk1yYtilrAWjRUgfHRZoEkY9s+oiqOxnth3z68wC2MWYx7ykkS5hhDamixg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/utils": "^10.9.0",
|
||||
"@graphql-tools/utils": "^10.9.1",
|
||||
"@types/ws": "^8.0.0",
|
||||
"isomorphic-ws": "^5.0.0",
|
||||
"tslib": "^2.4.0",
|
||||
@@ -1608,14 +1625,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/git-loader": {
|
||||
"version": "8.0.25",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/git-loader/-/git-loader-8.0.25.tgz",
|
||||
"integrity": "sha512-Zp9GtGfbnqwaFCUYQmTzJ3uKDgvHQfkaYSAQp+ZBKUrKu/m/TG6oxoy6duFYKujh7+fB0fhHYPJXdkGTSemBHA==",
|
||||
"version": "8.0.26",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/git-loader/-/git-loader-8.0.26.tgz",
|
||||
"integrity": "sha512-0g+9eng8DaT4ZmZvUmPgjLTgesUa6M8xrDjNBltRldZkB055rOeUgJiKmL6u8PjzI5VxkkVsn0wtAHXhDI2UXQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/graphql-tag-pluck": "8.3.20",
|
||||
"@graphql-tools/utils": "^10.9.0",
|
||||
"@graphql-tools/graphql-tag-pluck": "8.3.21",
|
||||
"@graphql-tools/utils": "^10.9.1",
|
||||
"is-glob": "4.0.3",
|
||||
"micromatch": "^4.0.8",
|
||||
"tslib": "^2.4.0",
|
||||
@@ -1629,15 +1646,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/github-loader": {
|
||||
"version": "8.0.21",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/github-loader/-/github-loader-8.0.21.tgz",
|
||||
"integrity": "sha512-bXy8XDRz8YqMLZM7s6XW6eeADCjyAvlyUENBwP3pN9AyTh6xN61EHruFLbaMaGnQOlKITohxFM4mrrcRWJ1Iog==",
|
||||
"version": "8.0.22",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/github-loader/-/github-loader-8.0.22.tgz",
|
||||
"integrity": "sha512-uQ4JNcNPsyMkTIgzeSbsoT9hogLjYrZooLUYd173l5eUGUi49EAcsGdiBCKaKfEjanv410FE8hjaHr7fjSRkJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/executor-http": "^1.1.9",
|
||||
"@graphql-tools/graphql-tag-pluck": "^8.3.20",
|
||||
"@graphql-tools/utils": "^10.9.0",
|
||||
"@graphql-tools/graphql-tag-pluck": "^8.3.21",
|
||||
"@graphql-tools/utils": "^10.9.1",
|
||||
"@whatwg-node/fetch": "^0.10.0",
|
||||
"@whatwg-node/promise-helpers": "^1.0.0",
|
||||
"sync-fetch": "0.6.0-2",
|
||||
@@ -1651,14 +1668,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/graphql-file-loader": {
|
||||
"version": "8.0.21",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-8.0.21.tgz",
|
||||
"integrity": "sha512-E11KcRIIM6W04mDV95kx7SDrbqVD58jP3O1227JfBddzOx5q5Rb2b/1Sxw1+eNnGZT+xdT/506SrJ5dhLtwUrA==",
|
||||
"version": "8.0.22",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-8.0.22.tgz",
|
||||
"integrity": "sha512-KFUbjXgWr5+w/AioOuIuULy4LwcyDuQqTRFQGe+US1d9Z4+ZopcJLwsJTqp5B+icDkCqld4paN0y0qi9MrIvbg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/import": "7.0.20",
|
||||
"@graphql-tools/utils": "^10.9.0",
|
||||
"@graphql-tools/import": "7.0.21",
|
||||
"@graphql-tools/utils": "^10.9.1",
|
||||
"globby": "^11.0.3",
|
||||
"tslib": "^2.4.0",
|
||||
"unixify": "^1.0.0"
|
||||
@@ -1671,9 +1688,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/graphql-tag-pluck": {
|
||||
"version": "8.3.20",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-8.3.20.tgz",
|
||||
"integrity": "sha512-HBukyPzrS3GyWkBkB/vblN+Fhb+tBKWL9rEHaexxTU+J8YHkXHAYlLvu56NXcCBzpVGWP2ghJqPh+ZPaqaiThQ==",
|
||||
"version": "8.3.21",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-8.3.21.tgz",
|
||||
"integrity": "sha512-TJhELNvR1tmghXMi6HVKp/Swxbx1rcSp/zdkuJZT0DCM3vOY11FXY6NW3aoxumcuYDNN3jqXcCPKstYGFPi5GQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1682,7 +1699,7 @@
|
||||
"@babel/plugin-syntax-import-assertions": "^7.26.0",
|
||||
"@babel/traverse": "^7.26.10",
|
||||
"@babel/types": "^7.26.10",
|
||||
"@graphql-tools/utils": "^10.9.0",
|
||||
"@graphql-tools/utils": "^10.9.1",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1693,13 +1710,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/import": {
|
||||
"version": "7.0.20",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/import/-/import-7.0.20.tgz",
|
||||
"integrity": "sha512-Mz+1hBRnQYr4R5hdxc0to//v7V0OsBZH8BHbZgKvM5ayIBFl3+ArQFlfitukmrvZLmmi7UwordW3RG2yLjSx8A==",
|
||||
"version": "7.0.21",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/import/-/import-7.0.21.tgz",
|
||||
"integrity": "sha512-bcAqNWm/gLVEOy55o/WdaROERpDyUEmIfZ9E6NDjVk1ZGWfZe47+RgriTV80j6J5S5J1g+6loFkVWGAMqdN06g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/utils": "^10.9.0",
|
||||
"@graphql-tools/utils": "^10.9.1",
|
||||
"@theguild/federation-composition": "^0.19.0",
|
||||
"resolve-from": "5.0.0",
|
||||
"tslib": "^2.4.0"
|
||||
@@ -1712,13 +1729,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/json-file-loader": {
|
||||
"version": "8.0.19",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/json-file-loader/-/json-file-loader-8.0.19.tgz",
|
||||
"integrity": "sha512-msohJvmtlunrcFQJSVX1JOwd2hR6bewENY2LciX4zPrFRQqWc4LsYhU1S0X92iiBxpyz/tff+sJH/6ubncWlRg==",
|
||||
"version": "8.0.20",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/json-file-loader/-/json-file-loader-8.0.20.tgz",
|
||||
"integrity": "sha512-5v6W+ZLBBML5SgntuBDLsYoqUvwfNboAwL6BwPHi3z/hH1f8BS9/0+MCW9OGY712g7E4pc3y9KqS67mWF753eA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/utils": "^10.9.0",
|
||||
"@graphql-tools/utils": "^10.9.1",
|
||||
"globby": "^11.0.3",
|
||||
"tslib": "^2.4.0",
|
||||
"unixify": "^1.0.0"
|
||||
@@ -1731,14 +1748,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/load": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/load/-/load-8.1.1.tgz",
|
||||
"integrity": "sha512-hqxk+8VHQcl68UFuuTx46DesAJmjQdiGxjicNoB4m4nqk6itWtPYn7Qj9W9iq95PvbicWQasrAQ2srUbIoWE2A==",
|
||||
"version": "8.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/load/-/load-8.1.2.tgz",
|
||||
"integrity": "sha512-WhDPv25/jRND+0uripofMX0IEwo6mrv+tJg6HifRmDu8USCD7nZhufT0PP7lIcuutqjIQFyogqT70BQsy6wOgw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/schema": "^10.0.24",
|
||||
"@graphql-tools/utils": "^10.9.0",
|
||||
"@graphql-tools/schema": "^10.0.25",
|
||||
"@graphql-tools/utils": "^10.9.1",
|
||||
"p-limit": "3.1.0",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
@@ -1750,14 +1767,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/merge": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.1.0.tgz",
|
||||
"integrity": "sha512-mKmjIVeu4ayPr+LbuhzukBOd67YdLhe9uPO/2tQ74iXP0EQMPlzAbUGPPym92gqCT5SxM6kXT65JUE9oBRX0sQ==",
|
||||
"version": "9.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.1.1.tgz",
|
||||
"integrity": "sha512-BJ5/7Y7GOhTuvzzO5tSBFL4NGr7PVqTJY3KeIDlVTT8YLcTXtBR+hlrC3uyEym7Ragn+zyWdHeJ9ev+nRX1X2w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/utils": "^10.9.0",
|
||||
"@theguild/federation-composition": "^0.19.0",
|
||||
"@graphql-tools/utils": "^10.9.1",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1815,14 +1831,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/relay-operation-optimizer": {
|
||||
"version": "7.0.20",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-7.0.20.tgz",
|
||||
"integrity": "sha512-8xl03O/xwME4oRP7BEQEI8OI+ph3oDqQapNEV3X5UIxxLwAj6EKtpXR0mr2LSN9Ico6phrj8cEwVY+hBqAMo0w==",
|
||||
"version": "7.0.21",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-7.0.21.tgz",
|
||||
"integrity": "sha512-vMdU0+XfeBh9RCwPqRsr3A05hPA3MsahFn/7OAwXzMySA5EVnSH5R4poWNs3h1a0yT0tDPLhxORhK7qJdSWj2A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ardatan/relay-compiler": "^12.0.3",
|
||||
"@graphql-tools/utils": "^10.9.0",
|
||||
"@graphql-tools/utils": "^10.9.1",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1833,14 +1849,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/schema": {
|
||||
"version": "10.0.24",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.24.tgz",
|
||||
"integrity": "sha512-SQfYg31/L4EShTygz9I/+Issa3IDS7DFB/gd7AvWeICCNMDm0917QmLDYpVaCmgvzeky7JPeXaJEd0OtZNIW4Q==",
|
||||
"version": "10.0.25",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.25.tgz",
|
||||
"integrity": "sha512-/PqE8US8kdQ7lB9M5+jlW8AyVjRGCKU7TSktuW3WNKSKmDO0MK1wakvb5gGdyT49MjAIb4a3LWxIpwo5VygZuw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/merge": "^9.1.0",
|
||||
"@graphql-tools/utils": "^10.9.0",
|
||||
"@graphql-tools/merge": "^9.1.1",
|
||||
"@graphql-tools/utils": "^10.9.1",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1851,16 +1867,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/url-loader": {
|
||||
"version": "8.0.32",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-8.0.32.tgz",
|
||||
"integrity": "sha512-dr4eu+/Twbq6bS4O2ASi6EdTLC2bcxo+Iw0j1eDkonw+U5lK/2+aHF/bWRXVTMYMrWOLxv0+iYeGVe/zMjDbEg==",
|
||||
"version": "8.0.33",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-8.0.33.tgz",
|
||||
"integrity": "sha512-Fu626qcNHcqAj8uYd7QRarcJn5XZ863kmxsg1sm0fyjyfBJnsvC7ddFt6Hayz5kxVKfsnjxiDfPMXanvsQVBKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/executor-graphql-ws": "^2.0.1",
|
||||
"@graphql-tools/executor-http": "^1.1.9",
|
||||
"@graphql-tools/executor-legacy-ws": "^1.1.18",
|
||||
"@graphql-tools/utils": "^10.9.0",
|
||||
"@graphql-tools/executor-legacy-ws": "^1.1.19",
|
||||
"@graphql-tools/utils": "^10.9.1",
|
||||
"@graphql-tools/wrap": "^10.0.16",
|
||||
"@types/ws": "^8.0.0",
|
||||
"@whatwg-node/fetch": "^0.10.0",
|
||||
@@ -1878,9 +1894,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/utils": {
|
||||
"version": "10.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.9.0.tgz",
|
||||
"integrity": "sha512-LzFlJHNajdohRM+0pHTwcF9tZ0q7z5iZW0lwnTNJp7O6GYFcSvCQE5ijTQcXVQ/5WQf3SHn+Gpr56TR5XHmPtg==",
|
||||
"version": "10.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.9.1.tgz",
|
||||
"integrity": "sha512-B1wwkXk9UvU7LCBkPs8513WxOQ2H8Fo5p8HR1+Id9WmYE5+bd51vqN+MbrqvWczHCH2gwkREgHJN88tE0n1FCw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1898,15 +1914,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/wrap": {
|
||||
"version": "10.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-10.1.2.tgz",
|
||||
"integrity": "sha512-vjmPVrYCRelytltyzHy1+QP4mIBRcStjbDNsEC1TMth9KH9wGi3xToIjAAD4GTOnrc6UyZ9IqaIAhffEnhBTRQ==",
|
||||
"version": "10.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-10.1.3.tgz",
|
||||
"integrity": "sha512-YIcw7oZPlmlZKRBOQGNqKNY4lehB+U4NOP0BSuOd+23EZb8X7JjkruYUOjYsQ7GxS7aKmQpFbuqrfsLp9TRZnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/delegate": "^10.2.21",
|
||||
"@graphql-tools/schema": "^10.0.11",
|
||||
"@graphql-tools/utils": "^10.8.1",
|
||||
"@graphql-tools/delegate": "^10.2.22",
|
||||
"@graphql-tools/schema": "^10.0.24",
|
||||
"@graphql-tools/utils": "^10.9.0",
|
||||
"@whatwg-node/promise-helpers": "^1.3.0",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
@@ -2390,9 +2406,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.14.tgz",
|
||||
"integrity": "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==",
|
||||
"version": "24.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz",
|
||||
"integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3257,9 +3273,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.187",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.187.tgz",
|
||||
"integrity": "sha512-cl5Jc9I0KGUoOoSbxvTywTa40uspGJt/BDBoDLoxJRSBpWh4FFXBsjNRHfQrONsV/OoEjDfHUmZQa2d6Ze4YgA==",
|
||||
"version": "1.5.190",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.190.tgz",
|
||||
"integrity": "sha512-k4McmnB2091YIsdCgkS0fMVMPOJgxl93ltFzaryXqwip1AaxeDqKCGLxkXODDA5Ab/D+tV5EL5+aTx76RvLRxw==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -3294,9 +3310,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.6",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.6.tgz",
|
||||
"integrity": "sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==",
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz",
|
||||
"integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
@@ -3307,32 +3323,32 @@
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.25.6",
|
||||
"@esbuild/android-arm": "0.25.6",
|
||||
"@esbuild/android-arm64": "0.25.6",
|
||||
"@esbuild/android-x64": "0.25.6",
|
||||
"@esbuild/darwin-arm64": "0.25.6",
|
||||
"@esbuild/darwin-x64": "0.25.6",
|
||||
"@esbuild/freebsd-arm64": "0.25.6",
|
||||
"@esbuild/freebsd-x64": "0.25.6",
|
||||
"@esbuild/linux-arm": "0.25.6",
|
||||
"@esbuild/linux-arm64": "0.25.6",
|
||||
"@esbuild/linux-ia32": "0.25.6",
|
||||
"@esbuild/linux-loong64": "0.25.6",
|
||||
"@esbuild/linux-mips64el": "0.25.6",
|
||||
"@esbuild/linux-ppc64": "0.25.6",
|
||||
"@esbuild/linux-riscv64": "0.25.6",
|
||||
"@esbuild/linux-s390x": "0.25.6",
|
||||
"@esbuild/linux-x64": "0.25.6",
|
||||
"@esbuild/netbsd-arm64": "0.25.6",
|
||||
"@esbuild/netbsd-x64": "0.25.6",
|
||||
"@esbuild/openbsd-arm64": "0.25.6",
|
||||
"@esbuild/openbsd-x64": "0.25.6",
|
||||
"@esbuild/openharmony-arm64": "0.25.6",
|
||||
"@esbuild/sunos-x64": "0.25.6",
|
||||
"@esbuild/win32-arm64": "0.25.6",
|
||||
"@esbuild/win32-ia32": "0.25.6",
|
||||
"@esbuild/win32-x64": "0.25.6"
|
||||
"@esbuild/aix-ppc64": "0.25.8",
|
||||
"@esbuild/android-arm": "0.25.8",
|
||||
"@esbuild/android-arm64": "0.25.8",
|
||||
"@esbuild/android-x64": "0.25.8",
|
||||
"@esbuild/darwin-arm64": "0.25.8",
|
||||
"@esbuild/darwin-x64": "0.25.8",
|
||||
"@esbuild/freebsd-arm64": "0.25.8",
|
||||
"@esbuild/freebsd-x64": "0.25.8",
|
||||
"@esbuild/linux-arm": "0.25.8",
|
||||
"@esbuild/linux-arm64": "0.25.8",
|
||||
"@esbuild/linux-ia32": "0.25.8",
|
||||
"@esbuild/linux-loong64": "0.25.8",
|
||||
"@esbuild/linux-mips64el": "0.25.8",
|
||||
"@esbuild/linux-ppc64": "0.25.8",
|
||||
"@esbuild/linux-riscv64": "0.25.8",
|
||||
"@esbuild/linux-s390x": "0.25.8",
|
||||
"@esbuild/linux-x64": "0.25.8",
|
||||
"@esbuild/netbsd-arm64": "0.25.8",
|
||||
"@esbuild/netbsd-x64": "0.25.8",
|
||||
"@esbuild/openbsd-arm64": "0.25.8",
|
||||
"@esbuild/openbsd-x64": "0.25.8",
|
||||
"@esbuild/openharmony-arm64": "0.25.8",
|
||||
"@esbuild/sunos-x64": "0.25.8",
|
||||
"@esbuild/win32-arm64": "0.25.8",
|
||||
"@esbuild/win32-ia32": "0.25.8",
|
||||
"@esbuild/win32-x64": "0.25.8"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
@@ -3608,9 +3624,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/graphql-config/node_modules/jiti": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
|
||||
"integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
|
||||
"integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@@ -5859,15 +5875,15 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.0.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.5.tgz",
|
||||
"integrity": "sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==",
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.6.tgz",
|
||||
"integrity": "sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.6",
|
||||
"picomatch": "^4.0.2",
|
||||
"picomatch": "^4.0.3",
|
||||
"postcss": "^8.5.6",
|
||||
"rollup": "^4.40.0",
|
||||
"tinyglobby": "^0.2.14"
|
||||
|
20
package.json
20
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "publy-panel",
|
||||
"version": "0.7.8",
|
||||
"version": "0.7.9",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -12,26 +12,26 @@
|
||||
"codegen": "graphql-codegen --config codegen.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.0.6",
|
||||
"@biomejs/biome": "^2.1.2",
|
||||
"@graphql-codegen/cli": "^5.0.7",
|
||||
"@graphql-codegen/client-preset": "^4.8.3",
|
||||
"@graphql-codegen/typescript": "^4.0.6",
|
||||
"@graphql-codegen/typescript-operations": "^4.2.0",
|
||||
"@graphql-codegen/typescript-resolvers": "^4.0.6",
|
||||
"@types/node": "^24.0.7",
|
||||
"@graphql-codegen/typescript": "^4.1.6",
|
||||
"@graphql-codegen/typescript-operations": "^4.6.1",
|
||||
"@graphql-codegen/typescript-resolvers": "^4.5.1",
|
||||
"@types/node": "^24.1.0",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"graphql": "^16.11.0",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"lightningcss": "^1.30.0",
|
||||
"lightningcss": "^1.30.1",
|
||||
"prismjs": "^1.30.0",
|
||||
"solid-js": "^1.9.7",
|
||||
"terser": "^5.39.0",
|
||||
"terser": "^5.43.0",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^7.0.0",
|
||||
"vite": "^7.0.6",
|
||||
"vite-plugin-solid": "^2.11.7"
|
||||
},
|
||||
"overrides": {
|
||||
"vite": "^7.0.0"
|
||||
"vite": "^7.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@solidjs/router": "^0.15.3"
|
||||
|
@@ -1,7 +1,6 @@
|
||||
bcrypt
|
||||
PyJWT
|
||||
authlib
|
||||
passlib==1.7.4
|
||||
google-analytics-data
|
||||
colorlog
|
||||
psycopg2-binary
|
||||
@@ -12,6 +11,7 @@ starlette
|
||||
gql
|
||||
ariadne
|
||||
granian
|
||||
bcrypt
|
||||
|
||||
# NLP and search
|
||||
httpx
|
||||
@@ -21,7 +21,6 @@ pydantic
|
||||
trafilatura
|
||||
|
||||
types-requests
|
||||
types-passlib
|
||||
types-Authlib
|
||||
types-orjson
|
||||
types-PyYAML
|
||||
|
@@ -731,8 +731,8 @@ async def admin_get_reactions(
|
||||
"deleted_at": shout.deleted_at,
|
||||
},
|
||||
"stat": {
|
||||
"comments_count": stats.comments_count or 0,
|
||||
"rating": stats.rating or 0,
|
||||
"comments_count": stats.comments_count if stats else 0,
|
||||
"rating": stats.rating if stats else 0,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
@@ -609,22 +609,7 @@ def create_author(**kwargs) -> Author:
|
||||
# Получаем сообщество для назначения дефолтных ролей
|
||||
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"Не удалось инициализировать права сообщества {target_community_id}: {e}")
|
||||
|
||||
# Получаем дефолтные роли сообщества или используем стандартные
|
||||
try:
|
||||
default_roles = community.get_default_roles()
|
||||
if not default_roles:
|
||||
default_roles = ["reader", "author"]
|
||||
except AttributeError:
|
||||
default_roles = ["reader", "author"]
|
||||
|
||||
# Создаем CommunityAuthor с дефолтными ролями
|
||||
community_author = CommunityAuthor(
|
||||
|
@@ -72,19 +72,30 @@ 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:
|
||||
# Проверяем, что roles не None и не пустая строка
|
||||
if community_author.roles is not None and community_author.roles.strip():
|
||||
user_roles = community_author.role_list
|
||||
|
||||
# Добавляем синтетическую роль для системных админов
|
||||
@@ -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"]
|
||||
|
34
tests/auth/test_auth_service.py
Normal file
34
tests/auth/test_auth_service.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import pytest
|
||||
from services.auth import AuthService
|
||||
from services.db import local_session
|
||||
from auth.orm import Author
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ensure_user_has_reader_role():
|
||||
auth_service = AuthService()
|
||||
|
||||
# Создаем тестового пользователя без роли reader
|
||||
with local_session() as session:
|
||||
test_author = Author(
|
||||
email="test_reader_role@example.com",
|
||||
slug="test_reader_role",
|
||||
password="test_password"
|
||||
)
|
||||
session.add(test_author)
|
||||
session.commit()
|
||||
user_id = test_author.id
|
||||
|
||||
# Проверяем, что роль reader добавляется
|
||||
result = await auth_service.ensure_user_has_reader_role(user_id)
|
||||
assert result is True
|
||||
|
||||
# Проверяем, что при повторном вызове возвращается True
|
||||
result = await auth_service.ensure_user_has_reader_role(user_id)
|
||||
assert result is True
|
||||
|
||||
# Очищаем тестовые данные
|
||||
with local_session() as session:
|
||||
test_author = session.query(Author).filter_by(id=user_id).first()
|
||||
if test_author:
|
||||
session.delete(test_author)
|
||||
session.commit()
|
13
tests/auth/test_identity.py
Normal file
13
tests/auth/test_identity.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import pytest
|
||||
from auth.identity import Password
|
||||
|
||||
def test_password_verify():
|
||||
# Создаем пароль
|
||||
original_password = "test_password123"
|
||||
hashed_password = Password.encode(original_password)
|
||||
|
||||
# Проверяем корректный пароль
|
||||
assert Password.verify(original_password, hashed_password) is True
|
||||
|
||||
# Проверяем некорректный пароль
|
||||
assert Password.verify("wrong_password", hashed_password) is False
|
@@ -227,3 +227,51 @@ with (
|
||||
assert created_user is not None
|
||||
assert created_user.name == "Test User"
|
||||
assert created_user.email_verified is True
|
||||
|
||||
# Импортируем необходимые модели
|
||||
from orm.community import Community, CommunityAuthor
|
||||
|
||||
@pytest.fixture
|
||||
def test_community(oauth_db_session, simple_user):
|
||||
"""
|
||||
Создает тестовое сообщество с ожидаемыми ролями по умолчанию
|
||||
|
||||
Args:
|
||||
oauth_db_session: Сессия базы данных для теста
|
||||
simple_user: Пользователь для создания сообщества
|
||||
|
||||
Returns:
|
||||
Community: Созданное тестовое сообщество
|
||||
"""
|
||||
# Очищаем существующие записи
|
||||
oauth_db_session.query(Community).filter(
|
||||
(Community.id == 300) | (Community.slug == "test-oauth-community")
|
||||
).delete()
|
||||
oauth_db_session.commit()
|
||||
|
||||
# Создаем тестовое сообщество
|
||||
community = Community(
|
||||
id=300,
|
||||
name="Test OAuth Community",
|
||||
slug="test-oauth-community",
|
||||
desc="Community for OAuth tests",
|
||||
created_by=simple_user.id,
|
||||
settings={
|
||||
"default_roles": ["reader", "author"],
|
||||
"available_roles": ["reader", "author", "editor"]
|
||||
}
|
||||
)
|
||||
oauth_db_session.add(community)
|
||||
oauth_db_session.commit()
|
||||
|
||||
yield community
|
||||
|
||||
# Очистка после теста
|
||||
try:
|
||||
oauth_db_session.query(CommunityAuthor).filter(
|
||||
CommunityAuthor.community_id == community.id
|
||||
).delete()
|
||||
oauth_db_session.query(Community).filter(Community.id == community.id).delete()
|
||||
oauth_db_session.commit()
|
||||
except Exception:
|
||||
oauth_db_session.rollback()
|
||||
|
@@ -14,6 +14,7 @@ from auth.tokens.storage import TokenStorage
|
||||
async def test_token_storage(redis_client):
|
||||
"""Тест базовой функциональности TokenStorage с правильными fixtures"""
|
||||
|
||||
try:
|
||||
print("✅ Тестирование TokenStorage...")
|
||||
|
||||
# Тест создания сессии
|
||||
@@ -49,3 +50,9 @@ async def test_token_storage(redis_client):
|
||||
|
||||
print("✅ Все тесты пройдены успешно!")
|
||||
return True
|
||||
finally:
|
||||
# Безопасное закрытие клиента с использованием aclose()
|
||||
if hasattr(redis_client, 'aclose'):
|
||||
await redis_client.aclose()
|
||||
elif hasattr(redis_client, 'close'):
|
||||
await redis_client.close()
|
||||
|
@@ -3,7 +3,7 @@ from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.pool import StaticPool
|
||||
|
||||
from services.db import Base
|
||||
from orm.base import BaseModel as Base
|
||||
from services.redis import redis
|
||||
from tests.test_config import get_test_client
|
||||
|
||||
|
@@ -3,7 +3,7 @@
|
||||
|
||||
Проверяет работу AdminService и AuthService с RBAC системой.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import pytest
|
||||
|
||||
from auth.orm import Author
|
||||
@@ -11,6 +11,8 @@ from orm.community import Community, CommunityAuthor
|
||||
from services.admin import admin_service
|
||||
from services.auth import auth_service
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def simple_user(db_session):
|
||||
@@ -36,7 +38,7 @@ def simple_user(db_session):
|
||||
# Очистка после теста
|
||||
try:
|
||||
# Удаляем связанные записи CommunityAuthor
|
||||
db_session.query(CommunityAuthor).filter(CommunityAuthor.author_id == user.id).delete()
|
||||
db_session.query(CommunityAuthor).filter(CommunityAuthor.author_id == user.id).delete(synchronize_session=False)
|
||||
# Удаляем самого пользователя
|
||||
db_session.query(Author).filter(Author.id == user.id).delete()
|
||||
db_session.commit()
|
||||
@@ -48,17 +50,18 @@ def simple_user(db_session):
|
||||
def simple_community(db_session, simple_user):
|
||||
"""Создает простое тестовое сообщество"""
|
||||
# Очищаем любые существующие записи с этим ID/slug
|
||||
db_session.query(Community).filter(
|
||||
(Community.id == 200) | (Community.slug == "simple-test-community")
|
||||
).delete()
|
||||
db_session.query(Community).filter(Community.slug == "simple-test-community").delete()
|
||||
db_session.commit()
|
||||
|
||||
community = Community(
|
||||
id=200,
|
||||
name="Simple Test Community",
|
||||
slug="simple-test-community",
|
||||
desc="Simple community for tests",
|
||||
created_by=simple_user.id,
|
||||
settings={
|
||||
"default_roles": ["reader", "author"],
|
||||
"available_roles": ["reader", "author", "editor"]
|
||||
}
|
||||
)
|
||||
db_session.add(community)
|
||||
db_session.commit()
|
||||
@@ -76,6 +79,52 @@ def simple_community(db_session, simple_user):
|
||||
db_session.rollback()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_community(db_session, simple_user):
|
||||
"""
|
||||
Создает тестовое сообщество с ожидаемыми ролями по умолчанию
|
||||
|
||||
Args:
|
||||
db_session: Сессия базы данных для теста
|
||||
simple_user: Пользователь для создания сообщества
|
||||
|
||||
Returns:
|
||||
Community: Созданное тестовое сообщество
|
||||
"""
|
||||
# Очищаем существующие записи
|
||||
db_session.query(Community).filter(Community.slug == "test-rbac-community").delete()
|
||||
db_session.commit()
|
||||
|
||||
community = Community(
|
||||
name="Test RBAC Community",
|
||||
slug="test-rbac-community",
|
||||
desc="Community for RBAC tests",
|
||||
created_by=simple_user.id,
|
||||
settings={
|
||||
"default_roles": ["reader", "author"],
|
||||
"available_roles": ["reader", "author", "editor"]
|
||||
}
|
||||
)
|
||||
db_session.add(community)
|
||||
db_session.flush() # Получаем ID без коммита
|
||||
|
||||
logger.info(f"DEBUG: Создание Community с айди {community.id}")
|
||||
|
||||
db_session.commit()
|
||||
|
||||
yield community
|
||||
|
||||
# Очистка после теста
|
||||
try:
|
||||
# Удаляем связанные записи CommunityAuthor
|
||||
db_session.query(CommunityAuthor).filter(CommunityAuthor.community_id == community.id).delete()
|
||||
# Удаляем сообщество
|
||||
db_session.query(Community).filter(Community.id == community.id).delete()
|
||||
db_session.commit()
|
||||
except Exception:
|
||||
db_session.rollback()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def cleanup_test_users(db_session):
|
||||
"""Автоматически очищает тестовые записи пользователей перед каждым тестом"""
|
||||
@@ -96,7 +145,7 @@ def cleanup_test_users(db_session):
|
||||
existing_user = db_session.query(Author).filter(Author.email == email).first()
|
||||
if existing_user:
|
||||
# Удаляем связанные записи CommunityAuthor
|
||||
db_session.query(CommunityAuthor).filter(CommunityAuthor.author_id == existing_user.id).delete()
|
||||
db_session.query(CommunityAuthor).filter(CommunityAuthor.author_id == existing_user.id).delete(synchronize_session=False)
|
||||
# Удаляем пользователя
|
||||
db_session.delete(existing_user)
|
||||
db_session.commit()
|
||||
@@ -154,70 +203,101 @@ class TestSimpleAdminService:
|
||||
# Может быть пустой список или содержать системную роль админа
|
||||
assert len(roles) >= 0
|
||||
|
||||
def test_get_user_roles_with_roles(self, db_session, simple_user, simple_community):
|
||||
def test_get_user_roles_with_roles(self, db_session, simple_user, test_community):
|
||||
"""Тест получения ролей пользователя"""
|
||||
# Используем дефолтное сообщество (ID=1) для совместимости с AdminService
|
||||
default_community_id = 1
|
||||
# Используем тестовое сообщество
|
||||
community_id = test_community.id
|
||||
|
||||
print(f"DEBUG: user_id={simple_user.id}, community_id={default_community_id}")
|
||||
# Отладочная информация о тестовом сообществе
|
||||
logger.info(f"DEBUG: Тестовое сообщество ID: {community_id}")
|
||||
logger.info(f"DEBUG: Тестовое сообщество slug: {test_community.slug}")
|
||||
logger.info(f"DEBUG: Тестовое сообщество settings: {test_community.settings}")
|
||||
|
||||
# Полностью очищаем все существующие CommunityAuthor для пользователя
|
||||
existing_community_authors = db_session.query(CommunityAuthor).filter(
|
||||
CommunityAuthor.author_id == simple_user.id
|
||||
).all()
|
||||
|
||||
# Отладочная информация
|
||||
logger.info(f"DEBUG: Найдено существующих CommunityAuthor: {len(existing_community_authors)}")
|
||||
for ca in existing_community_authors:
|
||||
logger.info(f"DEBUG: Существующий CA - community_id: {ca.community_id}, roles: {ca.roles}")
|
||||
db_session.delete(ca)
|
||||
|
||||
# Очищаем существующие роли
|
||||
deleted_count = db_session.query(CommunityAuthor).filter(
|
||||
CommunityAuthor.author_id == simple_user.id,
|
||||
CommunityAuthor.community_id == default_community_id
|
||||
).delete()
|
||||
db_session.commit()
|
||||
print(f"DEBUG: Удалено записей CommunityAuthor: {deleted_count}")
|
||||
|
||||
# Создаем CommunityAuthor с ролями в дефолтном сообществе
|
||||
# Создаем CommunityAuthor с ролями в тестовом сообществе
|
||||
ca = CommunityAuthor(
|
||||
community_id=default_community_id,
|
||||
community_id=community_id,
|
||||
author_id=simple_user.id,
|
||||
)
|
||||
|
||||
# Расширенная отладка перед set_roles
|
||||
logger.info(f"DEBUG: Перед set_roles")
|
||||
logger.info(f"DEBUG: ca.roles до set_roles: {ca.roles}")
|
||||
logger.info(f"DEBUG: ca.role_list до set_roles: {ca.role_list}")
|
||||
|
||||
ca.set_roles(["reader", "author"])
|
||||
print(f"DEBUG: Установлены роли: {ca.role_list}")
|
||||
|
||||
# Расширенная отладка после set_roles
|
||||
logger.info(f"DEBUG: После set_roles")
|
||||
logger.info(f"DEBUG: ca.roles после set_roles: {ca.roles}")
|
||||
logger.info(f"DEBUG: ca.role_list после set_roles: {ca.role_list}")
|
||||
|
||||
db_session.add(ca)
|
||||
db_session.commit()
|
||||
print(f"DEBUG: CA сохранен в БД с ID: {ca.id}")
|
||||
|
||||
# Проверяем что роли сохранились в БД
|
||||
saved_ca = db_session.query(CommunityAuthor).filter(
|
||||
# Явная проверка сохранения CommunityAuthor
|
||||
check_ca = db_session.query(CommunityAuthor).filter(
|
||||
CommunityAuthor.author_id == simple_user.id,
|
||||
CommunityAuthor.community_id == default_community_id
|
||||
CommunityAuthor.community_id == community_id
|
||||
).first()
|
||||
assert saved_ca is not None
|
||||
print(f"DEBUG: Сохраненные роли в БД: {saved_ca.role_list}")
|
||||
assert "reader" in saved_ca.role_list
|
||||
assert "author" in saved_ca.role_list
|
||||
|
||||
# Проверяем роли через AdminService (использует дефолтное сообщество)
|
||||
logger.info(f"DEBUG: Проверка сохраненной записи CommunityAuthor")
|
||||
logger.info(f"DEBUG: Найденная запись: {check_ca}")
|
||||
logger.info(f"DEBUG: Роли в найденной записи: {check_ca.roles}")
|
||||
logger.info(f"DEBUG: role_list найденной записи: {check_ca.role_list}")
|
||||
|
||||
assert check_ca is not None, "CommunityAuthor должен быть сохранен в базе данных"
|
||||
assert check_ca.roles is not None, "Роли CommunityAuthor не должны быть None"
|
||||
assert "reader" in check_ca.role_list, "Роль 'reader' должна быть в role_list"
|
||||
assert "author" in check_ca.role_list, "Роль 'author' должна быть в role_list"
|
||||
|
||||
# Проверяем роли через AdminService
|
||||
from services.admin import admin_service
|
||||
from services.db import local_session
|
||||
|
||||
# Используем ту же сессию для проверки
|
||||
fresh_user = db_session.query(Author).filter(Author.id == simple_user.id).first()
|
||||
roles = admin_service.get_user_roles(fresh_user) # Без указания community_id - использует дефолт
|
||||
print(f"DEBUG: AdminService вернул роли: {roles}")
|
||||
assert "reader" in roles
|
||||
assert "author" in roles
|
||||
roles = admin_service.get_user_roles(fresh_user, community_id)
|
||||
|
||||
# Проверяем роли
|
||||
assert isinstance(roles, list), "Роли должны быть списком"
|
||||
assert "reader" in roles, "Роль 'reader' должна присутствовать"
|
||||
assert "author" in roles, "Роль 'author' должна присутствовать"
|
||||
assert len(roles) == 2, f"Должно быть 2 роли, а не {len(roles)}"
|
||||
|
||||
def test_update_user_success(self, db_session, simple_user):
|
||||
"""Тест успешного обновления пользователя"""
|
||||
original_name = simple_user.name
|
||||
from services.admin import admin_service
|
||||
|
||||
user_data = {
|
||||
# Обновляем пользователя
|
||||
result = admin_service.update_user({
|
||||
"id": simple_user.id,
|
||||
"email": simple_user.email,
|
||||
"name": "Updated Name",
|
||||
"roles": ["reader"]
|
||||
}
|
||||
"email": simple_user.email
|
||||
})
|
||||
|
||||
result = admin_service.update_user(user_data)
|
||||
assert result["success"] is True
|
||||
# Проверяем обновленного пользователя
|
||||
assert result is not None, "Пользователь должен быть обновлен"
|
||||
assert result.get("name") == "Updated Name", "Имя пользователя должно быть обновлено"
|
||||
|
||||
# Получаем обновленного пользователя из БД заново
|
||||
updated_user = db_session.query(Author).filter(Author.id == simple_user.id).first()
|
||||
assert updated_user.name == "Updated Name"
|
||||
|
||||
# Восстанавливаем исходное имя для других тестов
|
||||
updated_user.name = original_name
|
||||
db_session.commit()
|
||||
# Восстанавливаем исходное имя
|
||||
admin_service.update_user({
|
||||
"id": simple_user.id,
|
||||
"name": "Simple User",
|
||||
"email": simple_user.email
|
||||
})
|
||||
|
||||
|
||||
class TestSimpleAuthService:
|
||||
@@ -227,11 +307,14 @@ class TestSimpleAuthService:
|
||||
"""Тест базового создания пользователя"""
|
||||
test_email = "test_create_unique@example.com"
|
||||
|
||||
# Удаляем пользователя если существует
|
||||
existing = db_session.query(Author).filter(Author.email == test_email).first()
|
||||
if existing:
|
||||
db_session.query(CommunityAuthor).filter(CommunityAuthor.author_id == existing.id).delete()
|
||||
db_session.delete(existing)
|
||||
# Найдем существующих пользователей с таким email
|
||||
existing_users = db_session.query(Author).filter(Author.email == test_email).all()
|
||||
|
||||
# Удаляем связанные записи CommunityAuthor для существующих пользователей
|
||||
for user in existing_users:
|
||||
db_session.query(CommunityAuthor).filter(CommunityAuthor.author_id == user.id).delete(synchronize_session=False)
|
||||
db_session.delete(user)
|
||||
|
||||
db_session.commit()
|
||||
|
||||
user_dict = {
|
||||
@@ -247,37 +330,102 @@ class TestSimpleAuthService:
|
||||
assert user.name == "Test Create User"
|
||||
|
||||
# Очистка
|
||||
db_session.query(CommunityAuthor).filter(CommunityAuthor.author_id == user.id).delete()
|
||||
try:
|
||||
db_session.query(CommunityAuthor).filter(CommunityAuthor.author_id == user.id).delete(synchronize_session=False)
|
||||
db_session.delete(user)
|
||||
db_session.commit()
|
||||
except Exception as e:
|
||||
# Если возникла ошибка при удалении, просто логируем ее
|
||||
print(f"Ошибка при очистке: {e}")
|
||||
db_session.rollback()
|
||||
|
||||
def test_create_user_with_community(self, db_session, simple_community):
|
||||
"""Тест создания пользователя с привязкой к сообществу"""
|
||||
test_email = "test_community_unique@example.com"
|
||||
def test_create_user_with_community(self, db_session):
|
||||
"""Проверяем создание пользователя в конкретном сообществе"""
|
||||
from services.auth import auth_service
|
||||
from services.rbac import initialize_community_permissions
|
||||
from auth.orm import Author
|
||||
import asyncio
|
||||
import uuid
|
||||
|
||||
# Удаляем пользователя если существует
|
||||
existing = db_session.query(Author).filter(Author.email == test_email).first()
|
||||
if existing:
|
||||
db_session.query(CommunityAuthor).filter(CommunityAuthor.author_id == existing.id).delete()
|
||||
db_session.delete(existing)
|
||||
db_session.commit()
|
||||
# Создаем тестового пользователя
|
||||
system_author = db_session.query(Author).filter(Author.slug == "system").first()
|
||||
if not system_author:
|
||||
system_author = Author(
|
||||
name="System",
|
||||
slug="system",
|
||||
email="system@test.local"
|
||||
)
|
||||
db_session.add(system_author)
|
||||
db_session.flush()
|
||||
|
||||
# Создаем тестовое сообщество
|
||||
unique_slug = f"simple-test-community-{uuid.uuid4()}"
|
||||
community = Community(
|
||||
name="Simple Test Community",
|
||||
slug=unique_slug,
|
||||
desc="Simple community for tests",
|
||||
created_by=system_author.id,
|
||||
settings={
|
||||
"default_roles": ["reader", "author"],
|
||||
"available_roles": ["reader", "author", "editor"]
|
||||
}
|
||||
)
|
||||
db_session.add(community)
|
||||
db_session.flush()
|
||||
|
||||
# Инициализируем права сообщества
|
||||
async def init_community_permissions():
|
||||
await initialize_community_permissions(community.id)
|
||||
|
||||
# Запускаем инициализацию в текущем event loop
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(init_community_permissions())
|
||||
|
||||
# Генерируем уникальные данные для каждого теста
|
||||
unique_email = f"test_community_unique_{uuid.uuid4()}@example.com"
|
||||
unique_name = f"Test Community User {uuid.uuid4()}"
|
||||
unique_slug = f"test-community-user-{uuid.uuid4()}"
|
||||
|
||||
user_dict = {
|
||||
"email": test_email,
|
||||
"name": "Test Community User",
|
||||
"slug": "test-community-user-unique",
|
||||
"name": unique_name,
|
||||
"email": unique_email,
|
||||
"slug": unique_slug
|
||||
}
|
||||
|
||||
user = auth_service.create_user(user_dict, community_id=simple_community.id)
|
||||
# Создаем пользователя в конкретном сообществе
|
||||
user = auth_service.create_user(user_dict, community_id=community.id)
|
||||
|
||||
assert user is not None
|
||||
assert user.email == test_email
|
||||
# Проверяем созданного пользователя
|
||||
assert user is not None, "Пользователь должен быть создан"
|
||||
assert user.email == unique_email.lower(), "Email должен быть в нижнем регистре"
|
||||
assert user.name == unique_name, "Имя пользователя должно совпадать"
|
||||
assert user.slug == unique_slug, "Slug пользователя должен совпадать"
|
||||
|
||||
# Очистка
|
||||
db_session.query(CommunityAuthor).filter(CommunityAuthor.author_id == user.id).delete()
|
||||
db_session.delete(user)
|
||||
# Проверяем роли
|
||||
from orm.community import get_user_roles_in_community
|
||||
|
||||
# Получаем роли
|
||||
roles = get_user_roles_in_community(user.id, community_id=community.id)
|
||||
|
||||
# Проверяем роли
|
||||
assert "reader" in roles, f"У нового пользователя должна быть роль 'reader' в сообществе {community.id}. Текущие роли: {roles}"
|
||||
assert "author" in roles, f"У нового пользователя должна быть роль 'author' в сообществе {community.id}. Текущие роли: {roles}"
|
||||
|
||||
# Коммитим изменения
|
||||
db_session.commit()
|
||||
|
||||
# Очищаем созданные объекты
|
||||
try:
|
||||
# Удаляем связанные записи CommunityAuthor
|
||||
db_session.query(CommunityAuthor).filter(CommunityAuthor.author_id == user.id).delete()
|
||||
# Удаляем пользователя
|
||||
db_session.query(Author).filter(Author.id == user.id).delete()
|
||||
# Удаляем сообщество
|
||||
db_session.query(Community).filter(Community.id == community.id).delete()
|
||||
db_session.commit()
|
||||
except Exception:
|
||||
db_session.rollback()
|
||||
|
||||
|
||||
class TestCommunityAuthorMethods:
|
||||
"""Тесты методов CommunityAuthor"""
|
||||
|
Reference in New Issue
Block a user