- Добавлена поддержка персональных AI моделей для каждого пользователя - Оптимизация создания заданий: батч-запрос к AI вместо N запросов - Фильтрация слов по языку изучения (source_lang) в словаре - Удалены неиспользуемые колонки examples и category из vocabulary - Миграции для ai_model_id и удаления колонок 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
200 lines
6.9 KiB
Python
200 lines
6.9 KiB
Python
from sqlalchemy import select
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
from database.models import User, LanguageLevel
|
||
from typing import Optional
|
||
from utils.levels import set_user_level_for_language, get_default_level
|
||
|
||
|
||
class UserService:
|
||
"""Сервис для работы с пользователями"""
|
||
|
||
@staticmethod
|
||
async def get_or_create_user(session: AsyncSession, telegram_id: int, username: Optional[str] = None) -> User:
|
||
"""
|
||
Получить пользователя или создать нового
|
||
|
||
Args:
|
||
session: Сессия базы данных
|
||
telegram_id: Telegram ID пользователя
|
||
username: Username пользователя
|
||
|
||
Returns:
|
||
Объект пользователя
|
||
"""
|
||
# Попытка найти существующего пользователя
|
||
result = await session.execute(
|
||
select(User).where(User.telegram_id == telegram_id)
|
||
)
|
||
user = result.scalar_one_or_none()
|
||
|
||
if user:
|
||
return user
|
||
|
||
# Создание нового пользователя
|
||
new_user = User(
|
||
telegram_id=telegram_id,
|
||
username=username,
|
||
level=LanguageLevel.A1
|
||
)
|
||
session.add(new_user)
|
||
await session.commit()
|
||
await session.refresh(new_user)
|
||
|
||
return new_user
|
||
|
||
@staticmethod
|
||
async def get_user_by_telegram_id(session: AsyncSession, telegram_id: int) -> Optional[User]:
|
||
"""
|
||
Получить пользователя по Telegram ID
|
||
|
||
Args:
|
||
session: Сессия базы данных
|
||
telegram_id: Telegram ID пользователя
|
||
|
||
Returns:
|
||
Объект пользователя или None
|
||
"""
|
||
result = await session.execute(
|
||
select(User).where(User.telegram_id == telegram_id)
|
||
)
|
||
return result.scalar_one_or_none()
|
||
|
||
@staticmethod
|
||
async def get_user_by_id(session: AsyncSession, user_id: int) -> Optional[User]:
|
||
"""
|
||
Получить пользователя по внутреннему ID
|
||
|
||
Args:
|
||
session: Сессия базы данных
|
||
user_id: ID пользователя в БД
|
||
|
||
Returns:
|
||
Объект пользователя или None
|
||
"""
|
||
result = await session.execute(
|
||
select(User).where(User.id == user_id)
|
||
)
|
||
return result.scalar_one_or_none()
|
||
|
||
@staticmethod
|
||
async def update_user_level(session: AsyncSession, user_id: int, level: str, language: str = None):
|
||
"""
|
||
Обновить уровень пользователя для языка изучения.
|
||
|
||
Args:
|
||
session: Сессия базы данных
|
||
user_id: ID пользователя
|
||
level: Новый уровень (строка, например "B1" или "N4")
|
||
language: Язык (если None, берётся learning_language пользователя)
|
||
"""
|
||
result = await session.execute(
|
||
select(User).where(User.id == user_id)
|
||
)
|
||
user = result.scalar_one_or_none()
|
||
|
||
if user:
|
||
# Сохраняем в JSON для всех языков
|
||
set_user_level_for_language(user, level, language)
|
||
# Для обратной совместимости обновляем старое поле level (только для CEFR)
|
||
if level in ["A1", "A2", "B1", "B2", "C1", "C2"]:
|
||
user.level = LanguageLevel[level]
|
||
await session.commit()
|
||
|
||
@staticmethod
|
||
async def update_user_language(session: AsyncSession, user_id: int, language: str):
|
||
"""
|
||
Обновить язык интерфейса пользователя
|
||
|
||
Args:
|
||
session: Сессия базы данных
|
||
user_id: ID пользователя
|
||
language: Новый язык (ru/en)
|
||
"""
|
||
result = await session.execute(
|
||
select(User).where(User.id == user_id)
|
||
)
|
||
user = result.scalar_one_or_none()
|
||
|
||
if user:
|
||
user.language_interface = language
|
||
await session.commit()
|
||
|
||
@staticmethod
|
||
async def update_user_learning_language(session: AsyncSession, user_id: int, language: str):
|
||
"""
|
||
Обновить язык изучения пользователя
|
||
|
||
Args:
|
||
session: Сессия базы данных
|
||
user_id: ID пользователя
|
||
language: Новый язык изучения (ISO2)
|
||
"""
|
||
result = await session.execute(
|
||
select(User).where(User.id == user_id)
|
||
)
|
||
user = result.scalar_one_or_none()
|
||
|
||
if user:
|
||
user.learning_language = language
|
||
await session.commit()
|
||
|
||
@staticmethod
|
||
async def update_user_translation_language(session: AsyncSession, user_id: int, language: str):
|
||
"""
|
||
Обновить язык перевода пользователя
|
||
|
||
Args:
|
||
session: Сессия базы данных
|
||
user_id: ID пользователя
|
||
language: Новый язык перевода (ru/en/ja)
|
||
"""
|
||
result = await session.execute(
|
||
select(User).where(User.id == user_id)
|
||
)
|
||
user = result.scalar_one_or_none()
|
||
|
||
if user:
|
||
user.translation_language = language
|
||
await session.commit()
|
||
|
||
@staticmethod
|
||
async def update_user_tasks_count(session: AsyncSession, user_id: int, count: int):
|
||
"""
|
||
Обновить количество заданий пользователя
|
||
|
||
Args:
|
||
session: Сессия базы данных
|
||
user_id: ID пользователя
|
||
count: Количество заданий (5-15)
|
||
"""
|
||
# Валидация диапазона
|
||
count = max(5, min(15, count))
|
||
|
||
result = await session.execute(
|
||
select(User).where(User.id == user_id)
|
||
)
|
||
user = result.scalar_one_or_none()
|
||
|
||
if user:
|
||
user.tasks_count = count
|
||
await session.commit()
|
||
|
||
@staticmethod
|
||
async def update_user_ai_model(session: AsyncSession, user_id: int, model_id: Optional[int]):
|
||
"""
|
||
Обновить AI модель пользователя
|
||
|
||
Args:
|
||
session: Сессия базы данных
|
||
user_id: ID пользователя
|
||
model_id: ID модели или None для глобальной
|
||
"""
|
||
result = await session.execute(
|
||
select(User).where(User.id == user_id)
|
||
)
|
||
user = result.scalar_one_or_none()
|
||
|
||
if user:
|
||
user.ai_model_id = model_id
|
||
await session.commit()
|