Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion backend/src/cms_backend/api/routes/books.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
from cms_backend.db.book import get_book as db_get_book
from cms_backend.db.book import move_book as db_move_book
from cms_backend.db.book import recover_book as db_recover_book
from cms_backend.db.books import get_book_languages as db_get_book_languages
from cms_backend.db.books import get_books as db_get_books
from cms_backend.db.books import get_zim_urls as db_get_zim_urls
from cms_backend.schemas import BaseModel
from cms_backend.schemas.models import ZimUrlsSchema
from cms_backend.schemas.models import BookLanguagesSchema, ZimUrlsSchema
from cms_backend.schemas.orms import (
BookFullSchema,
BookLightSchema,
Expand Down Expand Up @@ -80,6 +81,13 @@ def get_zim_urls(
return db_get_zim_urls(session, zim_ids)


@router.get("/languages")
def get_book_languages(
session: Annotated[OrmSession, Depends(gen_dbsession)],
) -> BookLanguagesSchema:
return db_get_book_languages(session)


@router.get("/{book_id}")
def get_book(
book_id: Annotated[UUID, Path()],
Expand Down
19 changes: 18 additions & 1 deletion backend/src/cms_backend/db/books.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from cms_backend.db import count_from_stmt
from cms_backend.db.models import Book, BookLocation, Collection, CollectionTitle, Title
from cms_backend.schemas.models import ZimUrlSchema, ZimUrlsSchema
from cms_backend.schemas.models import BookLanguagesSchema, ZimUrlSchema, ZimUrlsSchema
from cms_backend.schemas.orms import BookLightSchema, ListResult
from cms_backend.utils.filename import construct_download_url

Expand Down Expand Up @@ -197,3 +197,20 @@ def get_zim_urls(session: OrmSession, zim_ids: list[UUID]) -> ZimUrlsSchema:
)

return result


def get_book_languages(session: OrmSession) -> BookLanguagesSchema:
"""Get the sorted list of language codes used by production books."""
stmt = select(Book.zim_metadata).where(Book.location_kind == "prod")

languages: set[str] = set()
for zim_metadata in session.scalars(stmt):
language_value = zim_metadata.get("Language")
if not isinstance(language_value, str):
continue

for language in language_value.split(","):
if normalized_language := language.strip():
languages.add(normalized_language)

return BookLanguagesSchema(languages=sorted(languages))
4 changes: 4 additions & 0 deletions backend/src/cms_backend/schemas/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ class ZimUrlSchema(BaseModel):

class ZimUrlsSchema(BaseModel):
urls: dict[UUID, list[ZimUrlSchema]]


class BookLanguagesSchema(BaseModel):
languages: list[str]
30 changes: 30 additions & 0 deletions backend/tests/api/routes/test_books.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import pytest
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session as OrmSession

from cms_backend.db.models import Book, Title

Expand Down Expand Up @@ -258,6 +259,35 @@ def test_get_books_filter_by_id(
assert response_doc["items"][0]["id"] == str(book1.id)


def test_get_book_languages(
client: TestClient,
dbsession: OrmSession,
create_book: Callable[..., Book],
):
"""Test books languages endpoint returns sorted distinct production languages."""
prod_book = create_book(zim_metadata={"Language": "eng, fra"})
prod_book.location_kind = "prod"

other_prod_book = create_book(zim_metadata={"Language": " deu ,eng, ,spa "})
other_prod_book.location_kind = "prod"

create_book(zim_metadata={"Language": "hin"})
staging_book = create_book(zim_metadata={"Language": "ita"})
staging_book.location_kind = "staging"

blank_language_book = create_book(zim_metadata={"Language": " , "})
blank_language_book.location_kind = "prod"

missing_language_book = create_book(zim_metadata={"Name": "no-language"})
missing_language_book.location_kind = "prod"

dbsession.flush()

response = client.get("/v1/books/languages")
assert response.status_code == HTTPStatus.OK
assert response.json() == {"languages": ["deu", "eng", "fra", "spa"]}


def test_get_book_by_id(
client: TestClient,
book: Book,
Expand Down
30 changes: 29 additions & 1 deletion backend/tests/db/test_books.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
move_book,
recover_book,
)
from cms_backend.db.books import get_books, get_zim_urls
from cms_backend.db.books import get_book_languages, get_books, get_zim_urls
from cms_backend.db.exceptions import RecordDoesNotExistError
from cms_backend.db.models import (
Book,
Expand Down Expand Up @@ -375,6 +375,34 @@ def test_get_zim_urls(
assert view_url.collection == collection.name


def test_get_book_languages(
dbsession: OrmSession,
create_book: Callable[..., Book],
):
"""Return the sorted distinct language codes from production books only."""
prod_book = create_book(zim_metadata={"Language": "eng, fra"})
prod_book.location_kind = "prod"

other_prod_book = create_book(zim_metadata={"Language": " deu ,eng, ,spa "})
other_prod_book.location_kind = "prod"

create_book(zim_metadata={"Language": "hin"})
staging_book = create_book(zim_metadata={"Language": "ita"})
staging_book.location_kind = "staging"

blank_language_book = create_book(zim_metadata={"Language": " , "})
blank_language_book.location_kind = "prod"

missing_language_book = create_book(zim_metadata={"Name": "no-language"})
missing_language_book.location_kind = "prod"

dbsession.flush()

result = get_book_languages(dbsession)

assert result.languages == ["deu", "eng", "fra", "spa"]


def test_get_zim_urls_book_with_subpath(
dbsession: OrmSession,
create_book: Callable[..., Book],
Expand Down