from typing import Union from fastapi import HTTPException, status from app.models import Author as AuthorDB from app.models import Book as BookDB from app.serializers.book import CreateBook, CreateRemoteBook from app.services.common import ( TRGMSearchService, MeiliSearchService, GetRandomService, BaseFilterService, ) GET_OBJECT_IDS_QUERY = """ SELECT ARRAY( WITH filtered_books AS ( SELECT id, similarity(title, :query) as sml FROM books WHERE books.title % :query AND books.is_deleted = 'f' AND books.lang = ANY(:langs ::text[]) ) SELECT fbooks.id FROM filtered_books as fbooks ORDER BY fbooks.sml DESC, fbooks.id LIMIT 210 ); """ class BookTGRMSearchService(TRGMSearchService): MODEL_CLASS = BookDB PREFETCH_RELATED = ["source"] SELECT_RELATED = ["authors", "translators", "annotations"] GET_OBJECT_IDS_QUERY = GET_OBJECT_IDS_QUERY class BookFilterService(BaseFilterService): MODEL_CLASS = BookDB PREFETCH_RELATED = ["source"] SELECT_RELATED = ["authors", "translators", "annotations"] class BookCreator: @classmethod def _raise_bad_request(cls): raise HTTPException(status.HTTP_404_NOT_FOUND) @classmethod async def _create_book(cls, data: CreateBook) -> BookDB: data_dict = data.dict() author_ids = data_dict.pop("authors", []) authors = await AuthorDB.objects.filter(id__in=author_ids).all() if len(author_ids) != len(authors): cls._raise_bad_request() book = await BookDB.objects.create(**data_dict) for author in authors: await book.authors.add(author) return book @classmethod async def _create_remote_book(cls, data: CreateRemoteBook) -> BookDB: data_dict = data.dict() author_ids = data_dict.pop("remote_authors", []) authors = await AuthorDB.objects.filter( source__id=data.source, remote_id__in=author_ids ).all() if len(author_ids) != len(authors): cls._raise_bad_request() book = await BookDB.objects.create(**data_dict) for author in authors: await book.authors.add(author) return book @classmethod async def create(cls, data: Union[CreateBook, CreateRemoteBook]) -> BookDB: if isinstance(data, CreateBook): return await cls._create_book(data) if isinstance(data, CreateRemoteBook): return await cls._create_remote_book(data) GET_OBJECTS_ID_QUERY = """ WITH filtered_books AS ( SELECT id FROM books WHERE books.is_deleted = 'f' AND books.lang = ANY(:langs ::text[]) ) SELECT id FROM filtered_books; """ class GetRandomBookService(GetRandomService): MODEL_CLASS = BookDB GET_OBJECTS_ID_QUERY = GET_OBJECTS_ID_QUERY class BookMeiliSearchService(MeiliSearchService): MODEL_CLASS = BookDB PREFETCH_RELATED = ["source"] SELECT_RELATED = ["authors", "translators", "annotations"] MS_INDEX_NAME = "books" MS_INDEX_LANG_KEY = "lang"