172 lines
6.3 KiB
Python
172 lines
6.3 KiB
Python
from sqlalchemy import select
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
from database.models import Vocabulary, WordSource, LanguageLevel
|
||
from typing import List, Optional
|
||
import re
|
||
|
||
|
||
class VocabularyService:
|
||
"""Сервис для работы со словарным запасом"""
|
||
|
||
@staticmethod
|
||
async def add_word(
|
||
session: AsyncSession,
|
||
user_id: int,
|
||
word_original: str,
|
||
word_translation: str,
|
||
source_lang: Optional[str] = None,
|
||
translation_lang: Optional[str] = None,
|
||
transcription: Optional[str] = None,
|
||
examples: Optional[dict] = None,
|
||
category: Optional[str] = None,
|
||
difficulty_level: Optional[str] = None,
|
||
source: WordSource = WordSource.MANUAL,
|
||
notes: Optional[str] = None
|
||
) -> Vocabulary:
|
||
"""
|
||
Добавить слово в словарь пользователя
|
||
|
||
Args:
|
||
session: Сессия базы данных
|
||
user_id: ID пользователя
|
||
word_original: Оригинальное слово
|
||
word_translation: Перевод
|
||
transcription: Транскрипция
|
||
examples: Примеры использования
|
||
category: Категория слова
|
||
difficulty_level: Уровень сложности
|
||
source: Источник добавления
|
||
notes: Заметки пользователя
|
||
|
||
Returns:
|
||
Созданный объект слова
|
||
"""
|
||
# Преобразование difficulty_level в enum
|
||
difficulty_enum = None
|
||
if difficulty_level:
|
||
try:
|
||
difficulty_enum = LanguageLevel[difficulty_level]
|
||
except KeyError:
|
||
difficulty_enum = None
|
||
|
||
new_word = Vocabulary(
|
||
user_id=user_id,
|
||
word_original=word_original,
|
||
word_translation=word_translation,
|
||
source_lang=source_lang,
|
||
translation_lang=translation_lang,
|
||
transcription=transcription,
|
||
examples=examples,
|
||
category=category,
|
||
difficulty_level=difficulty_enum,
|
||
source=source,
|
||
notes=notes
|
||
)
|
||
|
||
session.add(new_word)
|
||
await session.commit()
|
||
await session.refresh(new_word)
|
||
|
||
return new_word
|
||
|
||
@staticmethod
|
||
@staticmethod
|
||
def _is_japanese(text: str) -> bool:
|
||
if not text:
|
||
return False
|
||
return re.search(r"[\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF]", text) is not None
|
||
|
||
@staticmethod
|
||
def _filter_by_learning_lang(words: List[Vocabulary], learning_lang: Optional[str]) -> List[Vocabulary]:
|
||
if not learning_lang:
|
||
return words
|
||
# Если в БД указан source_lang – фильтруем по нему.
|
||
with_lang = [w for w in words if getattr(w, 'source_lang', None)]
|
||
if with_lang:
|
||
return [w for w in words if (w.source_lang or '').lower() == learning_lang.lower()]
|
||
# Фолбэк-эвристика для японского, если язык не сохранён
|
||
if learning_lang.lower() == 'ja':
|
||
return [w for w in words if VocabularyService._is_japanese(w.word_original)]
|
||
return [w for w in words if not VocabularyService._is_japanese(w.word_original)]
|
||
|
||
@staticmethod
|
||
async def get_user_words(session: AsyncSession, user_id: int, limit: int = 50, learning_lang: Optional[str] = None) -> List[Vocabulary]:
|
||
"""
|
||
Получить все слова пользователя
|
||
|
||
Args:
|
||
session: Сессия базы данных
|
||
user_id: ID пользователя
|
||
limit: Максимальное количество слов
|
||
|
||
Returns:
|
||
Список слов пользователя
|
||
"""
|
||
result = await session.execute(
|
||
select(Vocabulary)
|
||
.where(Vocabulary.user_id == user_id)
|
||
.order_by(Vocabulary.created_at.desc())
|
||
)
|
||
words = list(result.scalars().all())
|
||
words = VocabularyService._filter_by_learning_lang(words, learning_lang)
|
||
return words[:limit]
|
||
|
||
@staticmethod
|
||
async def get_words_count(session: AsyncSession, user_id: int, learning_lang: Optional[str] = None) -> int:
|
||
"""
|
||
Получить количество слов в словаре пользователя
|
||
|
||
Args:
|
||
session: Сессия базы данных
|
||
user_id: ID пользователя
|
||
|
||
Returns:
|
||
Количество слов
|
||
"""
|
||
result = await session.execute(
|
||
select(Vocabulary).where(Vocabulary.user_id == user_id)
|
||
)
|
||
words = list(result.scalars().all())
|
||
words = VocabularyService._filter_by_learning_lang(words, learning_lang)
|
||
return len(words)
|
||
|
||
@staticmethod
|
||
async def find_word(session: AsyncSession, user_id: int, word: str) -> Optional[Vocabulary]:
|
||
"""
|
||
Найти слово в словаре пользователя
|
||
|
||
Args:
|
||
session: Сессия базы данных
|
||
user_id: ID пользователя
|
||
word: Слово для поиска
|
||
|
||
Returns:
|
||
Объект слова или None
|
||
"""
|
||
result = await session.execute(
|
||
select(Vocabulary)
|
||
.where(Vocabulary.user_id == user_id)
|
||
.where(Vocabulary.word_original.ilike(f"%{word}%"))
|
||
)
|
||
return result.scalar_one_or_none()
|
||
|
||
@staticmethod
|
||
async def get_word_by_original(session: AsyncSession, user_id: int, word: str) -> Optional[Vocabulary]:
|
||
"""
|
||
Получить слово по точному совпадению
|
||
|
||
Args:
|
||
session: Сессия базы данных
|
||
user_id: ID пользователя
|
||
word: Слово для поиска (точное совпадение)
|
||
|
||
Returns:
|
||
Объект слова или None
|
||
"""
|
||
result = await session.execute(
|
||
select(Vocabulary)
|
||
.where(Vocabulary.user_id == user_id)
|
||
.where(Vocabulary.word_original == word.lower())
|
||
)
|
||
return result.scalar_one_or_none()
|