diff --git a/fastapi_book_server/app/alembic/versions/08193b547a80_.py b/fastapi_book_server/app/alembic/versions/08193b547a80_.py index ff47c3b..1df9b01 100644 --- a/fastapi_book_server/app/alembic/versions/08193b547a80_.py +++ b/fastapi_book_server/app/alembic/versions/08193b547a80_.py @@ -90,6 +90,20 @@ def upgrade(): unique=False, postgresql_using="btree", ) + op.create_index( + op.f("translations_book"), + "translations", + ["book"], + unique=False, + postgresql_using="btree", + ) + op.create_index( + op.f("translations_author"), + "translations", + ["author"], + unique=False, + postgresql_using="btree", + ) # ### end Alembic commands ### @@ -108,4 +122,6 @@ def downgrade(): op.drop_index(op.f("book_authors_author"), table_name="book_authors") op.drop_index(op.f("book_sequences_book"), table_name="book_sequences") op.drop_index(op.f("book_sequences_sequence"), table_name="book_sequences") + op.drop_index(op.f("translations_book"), table_name="translations") + op.drop_index(op.f("translations_author"), table_name="translations") # ### end Alembic commands ### diff --git a/fastapi_book_server/app/services/common.py b/fastapi_book_server/app/services/common.py index f489bf1..ba1cbe6 100644 --- a/fastapi_book_server/app/services/common.py +++ b/fastapi_book_server/app/services/common.py @@ -19,6 +19,7 @@ class TRGMSearchService(Generic[T]): SELECT_RELATED: Optional[Union[list[str], str]] = None PREFETCH_RELATED: Optional[Union[list[str], str]] = None GET_OBJECT_IDS_QUERY: Optional[str] = None + CUSTOM_CACHE_PREFIX: Optional[str] = None CACHE_TTL = 60 * 60 @classmethod @@ -66,9 +67,14 @@ class TRGMSearchService(Generic[T]): return row["array"] + @classmethod + @property + def cache_prefix(cls) -> str: + return cls.CUSTOM_CACHE_PREFIX or cls.model.__class__.__name__ + @classmethod def get_cache_key(cls, query_data: str, allowed_langs: list[str]) -> str: - model_class_name = cls.model.__class__.__name__ + model_class_name = cls.cache_prefix allowed_langs_part = ",".join(allowed_langs) return f"{model_class_name}_{query_data}_{allowed_langs_part}" diff --git a/fastapi_book_server/app/services/translator.py b/fastapi_book_server/app/services/translator.py new file mode 100644 index 0000000..951a8c0 --- /dev/null +++ b/fastapi_book_server/app/services/translator.py @@ -0,0 +1,52 @@ +from app.models import Author +from app.services.common import TRGMSearchService + + +GET_OBJECT_IDS_QUERY = """ +SELECT ARRAY( + WITH filtered_authors AS ( + SELECT + id, + GREATEST( + similarity( + (last_name || ' ' || first_name || ' ' || middle_name), + :query + ), + similarity((last_name || ' ' || first_name), :query), + similarity((last_name), :query) + ) as sml, + ( + SELECT count(*) FROM translations + LEFT JOIN books + ON (books.id = book AND + books.is_deleted = 'f' AND + books.lang = ANY(:langs ::text[])) + WHERE author = authors.id + ) as books_count + FROM authors + WHERE ( + (last_name || ' ' || first_name || ' ' || middle_name) % :query OR + (last_name || ' ' || first_name) % :query OR + (last_name) % :query + ) AND + EXISTS ( + SELECT * FROM translations + LEFT JOIN books + ON (books.id = book AND + books.is_deleted = 'f' AND + books.lang = ANY(:langs ::text[])) + WHERE author = authors.id + ) + ) + SELECT fauthors.id FROM filtered_authors as fauthors + ORDER BY fauthors.sml DESC, fauthors.books_count DESC + LIMIT 210 +); +""" + + +class TranslatorTGRMSearchService(TRGMSearchService): + MODEL_CLASS = Author + CUSTOM_CACHE_PREFIX = "translator" + PREFETCH_RELATED = ["source", "annotations"] + GET_OBJECT_IDS_QUERY = GET_OBJECT_IDS_QUERY diff --git a/fastapi_book_server/app/views/__init__.py b/fastapi_book_server/app/views/__init__.py index 1e28172..bb242c4 100644 --- a/fastapi_book_server/app/views/__init__.py +++ b/fastapi_book_server/app/views/__init__.py @@ -1,4 +1,4 @@ -from app.views.author import author_router +from app.views.author import author_router, translator_router from app.views.author_annotation import author_annotation_router from app.views.book import book_router from app.views.book_annotation import book_annotation_router @@ -10,6 +10,7 @@ from app.views.translation import translation_router routers = [ source_router, author_router, + translator_router, author_annotation_router, book_router, book_annotation_router, diff --git a/fastapi_book_server/app/views/author.py b/fastapi_book_server/app/views/author.py index 0d61454..d61367c 100644 --- a/fastapi_book_server/app/views/author.py +++ b/fastapi_book_server/app/views/author.py @@ -16,6 +16,7 @@ from app.serializers.author import ( ) from app.serializers.author_annotation import AuthorAnnotation from app.services.author import AuthorTGRMSearchService, GetRandomAuthorService +from app.services.translator import TranslatorTGRMSearchService from app.utils.pagination import CustomPage @@ -97,19 +98,6 @@ async def get_author_books( ) -@author_router.get("/{id}/translated_books", response_model=CustomPage[TranslatedBook]) -async def get_translated_books( - id: int, allowed_langs: list[str] = Depends(get_allowed_langs) -): - return await paginate( - BookDB.objects.select_related(["source", "annotations", "translators"]).filter( - translations__translator__id=id, - lang__in=allowed_langs, - is_deleted=False, - ) - ) - - @author_router.get( "/search/{query}", response_model=CustomPage[Author], dependencies=[Depends(Params)] ) @@ -119,3 +107,34 @@ async def search_authors( return await AuthorTGRMSearchService.get( query, request.app.state.redis, allowed_langs ) + + +translator_router = APIRouter( + prefix="/api/v1/translators", + tags=["author"], + dependencies=[Depends(check_token)], +) + + +@translator_router.get("/{id}/books", response_model=CustomPage[TranslatedBook]) +async def get_translated_books( + id: int, allowed_langs: list[str] = Depends(get_allowed_langs) +): + return await paginate( + BookDB.objects.select_related(["source", "annotations", "authors"]).filter( + translators__id=id, + lang__in=allowed_langs, + is_deleted=False, + ) + ) + + +@translator_router.get( + "/search/{query}", response_model=CustomPage[Author], dependencies=[Depends(Params)] +) +async def search_translators( + query: str, request: Request, allowed_langs: list[str] = Depends(get_allowed_langs) +): + return await TranslatorTGRMSearchService.get( + query, request.app.state.redis, allowed_langs + )