2025-05-16 09:23:48 +03:00
|
|
|
|
import re
|
|
|
|
|
|
from urllib.parse import quote_plus
|
|
|
|
|
|
|
[0.9.7] - 2025-08-18
### 🔄 Изменения
- **SQLAlchemy KeyError** - исправление ошибки `KeyError: Reaction` при инициализации
- **Исправлена ошибка SQLAlchemy**: Устранена проблема `InvalidRequestError: When initializing mapper Mapper[Shout(shout)], expression Reaction failed to locate a name (Reaction)`
### 🧪 Тестирование
- **Исправление тестов** - адаптация к новой структуре моделей
- **RBAC инициализация** - добавление `rbac.initialize_rbac()` в `conftest.py`
- **Создан тест для getSession**: Добавлен комплексный тест `test_getSession_cookies.py` с проверкой всех сценариев
- **Покрытие edge cases**: Тесты проверяют работу с валидными/невалидными токенами, отсутствующими пользователями
- **Мокирование зависимостей**: Использование unittest.mock для изоляции тестируемого кода
### 🔧 Рефакторинг
- **Упрощена архитектура**: Убраны сложные конструкции с отложенными импортами, заменены на чистую архитектуру
- **Перемещение моделей** - `Author` и связанные модели перенесены в `orm/author.py`: Вынесены базовые модели пользователей (`Author`, `AuthorFollower`, `AuthorBookmark`, `AuthorRating`) из `orm.author` в отдельный модуль
- **Устранены циклические импорты**: Разорван цикл между `auth.core` → `orm.community` → `orm.author` через реструктуризацию архитектуры
- **Создан модуль `utils/password.py`**: Класс `Password` вынесен в utils для избежания циклических зависимостей
- **Оптимизированы импорты моделей**: Убран прямой импорт `Shout` из `orm/community.py`, заменен на строковые ссылки
### 🔧 Авторизация с cookies
- **getSession теперь работает с cookies**: Мутация `getSession` теперь может получать токен из httpOnly cookies даже без заголовка Authorization
- **Убрано требование авторизации**: `getSession` больше не требует декоратор `@login_required`, работает автономно
- **Поддержка dual-авторизации**: Токен может быть получен как из заголовка Authorization, так и из cookie `session_token`
- **Автоматическая установка cookies**: Middleware автоматически устанавливает httpOnly cookies при успешном `getSession`
- **Обновлена GraphQL схема**: `SessionInfo` теперь содержит поля `success`, `error` и опциональные `token`, `author`
- **Единообразная обработка токенов**: Все модули теперь используют централизованные функции для работы с токенами
- **Улучшена обработка ошибок**: Добавлена детальная валидация токенов и пользователей в `getSession`
- **Логирование операций**: Добавлены подробные логи для отслеживания процесса авторизации
### 📝 Документация
- **Обновлена схема GraphQL**: `SessionInfo` тип теперь соответствует новому формату ответа
- Обновлена документация RBAC
- Обновлена документация авторизации с cookies
2025-08-18 14:25:25 +03:00
|
|
|
|
from orm.author import Author
|
2025-08-17 17:56:31 +03:00
|
|
|
|
from storage.db import local_session
|
2025-05-16 09:23:48 +03:00
|
|
|
|
|
|
|
|
|
|
|
2025-07-31 18:55:59 +03:00
|
|
|
|
def replace_translit(src: str | None) -> str:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Транслитерация строки с русского на английский.
|
2025-05-16 09:23:48 +03:00
|
|
|
|
|
2025-07-31 18:55:59 +03:00
|
|
|
|
Args:
|
|
|
|
|
|
src (str | None): Исходная строка или None
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
str: Транслитерированная строка или пустая строка, если src None
|
|
|
|
|
|
"""
|
|
|
|
|
|
if src is None:
|
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
# Создаем словарь для замены, так как некоторые русские символы
|
|
|
|
|
|
# соответствуют нескольким латинским
|
2025-06-02 02:56:11 +03:00
|
|
|
|
translit_dict = {
|
|
|
|
|
|
"а": "a",
|
|
|
|
|
|
"б": "b",
|
|
|
|
|
|
"в": "v",
|
|
|
|
|
|
"г": "g",
|
|
|
|
|
|
"д": "d",
|
|
|
|
|
|
"е": "e",
|
|
|
|
|
|
"ё": "yo",
|
|
|
|
|
|
"ж": "zh",
|
|
|
|
|
|
"з": "z",
|
|
|
|
|
|
"и": "i",
|
|
|
|
|
|
"й": "y",
|
|
|
|
|
|
"к": "k",
|
|
|
|
|
|
"л": "l",
|
|
|
|
|
|
"м": "m",
|
|
|
|
|
|
"н": "n",
|
|
|
|
|
|
"о": "o",
|
|
|
|
|
|
"п": "p",
|
|
|
|
|
|
"р": "r",
|
|
|
|
|
|
"с": "s",
|
|
|
|
|
|
"т": "t",
|
|
|
|
|
|
"у": "u",
|
|
|
|
|
|
"ф": "f",
|
|
|
|
|
|
"х": "h",
|
|
|
|
|
|
"ц": "c",
|
|
|
|
|
|
"ч": "ch",
|
|
|
|
|
|
"ш": "sh",
|
|
|
|
|
|
"щ": "sch",
|
|
|
|
|
|
"ъ": "",
|
|
|
|
|
|
"ы": "y",
|
|
|
|
|
|
"ь": "",
|
|
|
|
|
|
"э": "e",
|
|
|
|
|
|
"ю": "yu",
|
|
|
|
|
|
"я": "ya",
|
|
|
|
|
|
".": "-",
|
|
|
|
|
|
}
|
2025-05-16 09:23:48 +03:00
|
|
|
|
|
2025-06-02 02:56:11 +03:00
|
|
|
|
result = ""
|
2025-07-31 18:55:59 +03:00
|
|
|
|
for char in src.lower():
|
2025-06-02 02:56:11 +03:00
|
|
|
|
result += translit_dict.get(char, char)
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def generate_unique_slug(src: str) -> str:
|
2025-05-16 09:23:48 +03:00
|
|
|
|
print("[resolvers.auth] generating slug from: " + src)
|
|
|
|
|
|
slug = replace_translit(src.lower())
|
|
|
|
|
|
slug = re.sub("[^0-9a-zA-Z]+", "-", slug)
|
|
|
|
|
|
if slug != src:
|
|
|
|
|
|
print("[resolvers.auth] translited name: " + slug)
|
|
|
|
|
|
c = 1
|
|
|
|
|
|
with local_session() as session:
|
|
|
|
|
|
user = session.query(Author).where(Author.slug == slug).first()
|
|
|
|
|
|
while user:
|
|
|
|
|
|
user = session.query(Author).where(Author.slug == slug).first()
|
|
|
|
|
|
slug = slug + "-" + str(c)
|
|
|
|
|
|
c += 1
|
|
|
|
|
|
if not user:
|
|
|
|
|
|
unique_slug = slug
|
|
|
|
|
|
print("[resolvers.auth] " + unique_slug)
|
|
|
|
|
|
return quote_plus(unique_slug.replace("'", "")).replace("+", "-")
|
2025-06-02 02:56:11 +03:00
|
|
|
|
|
|
|
|
|
|
# Fallback return если что-то пошло не так
|
|
|
|
|
|
return quote_plus(slug.replace("'", "")).replace("+", "-")
|