Skip to content
Merged
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: 5 additions & 5 deletions backend/src/cms_backend/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
from pydantic import ValidationError

from cms_backend.api.routes.books import router as books_router
from cms_backend.api.routes.collection import router as collection_router
from cms_backend.api.routes.healthcheck import router as healthcheck_router
from cms_backend.api.routes.http_errors import BadRequestError
from cms_backend.api.routes.library import router as library_router
from cms_backend.api.routes.titles import router as titles_router
from cms_backend.api.routes.warehouse_paths import router as warehouse_paths_router
from cms_backend.api.routes.zimfarm_notifications import (
router as zimfarm_notification_router,
)
from cms_backend.context import Context
from cms_backend.db.exceptions import (
RecordAlreadyExistsError,
RecordDisabledError,
Expand All @@ -30,7 +30,8 @@

@asynccontextmanager
async def lifespan(_: FastAPI):
upgrade_db_schema()
if Context.alembic_upgrade_head_on_start:
upgrade_db_schema()
yield


Expand Down Expand Up @@ -58,8 +59,7 @@ def create_app(*, debug: bool = True):
main_router.include_router(router=healthcheck_router)
main_router.include_router(router=titles_router)
main_router.include_router(router=books_router)
main_router.include_router(router=warehouse_paths_router)
main_router.include_router(router=library_router)
main_router.include_router(router=collection_router)

app.include_router(router=main_router)

Expand Down
16 changes: 4 additions & 12 deletions backend/src/cms_backend/api/routes/books.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
BookFullSchema,
BookLightSchema,
BookLocationSchema,
ProducerSchema,
)

router = APIRouter(prefix="/books", tags=["books"])
Expand Down Expand Up @@ -67,9 +66,8 @@ async def get_book(
# Separate current and target locations
current_locations = [
BookLocationSchema(
warehouse_path_id=location.warehouse_path_id,
warehouse_name=location.warehouse_path.warehouse.name,
folder_name=location.warehouse_path.folder_name,
warehouse_name=location.warehouse.name,
path=str(location.path),
filename=location.filename,
status=location.status,
)
Expand All @@ -79,9 +77,8 @@ async def get_book(

target_locations = [
BookLocationSchema(
warehouse_path_id=location.warehouse_path_id,
warehouse_name=location.warehouse_path.warehouse.name,
folder_name=location.warehouse_path.folder_name,
warehouse_name=location.warehouse.name,
path=str(location.path),
filename=location.filename,
status=location.status,
)
Expand All @@ -103,11 +100,6 @@ async def get_book(
zimcheck_result=db_book.zimcheck_result,
zim_metadata=db_book.zim_metadata,
events=db_book.events,
producer=ProducerSchema(
display_name=db_book.producer_display_name,
display_url=db_book.producer_display_url,
unique_id=db_book.producer_unique_id,
),
current_locations=current_locations,
target_locations=target_locations,
)
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
from sqlalchemy.orm import Session as OrmSession

from cms_backend.db import gen_dbsession
from cms_backend.db.exceptions import RecordDoesNotExistError
from cms_backend.db.library import (
get_latest_books_for_library,
get_library,
get_library_by_name_or_none,
from cms_backend.db.collection import (
get_collection,
get_collection_by_name_or_none,
get_latest_books_for_collection,
)
from cms_backend.db.exceptions import RecordDoesNotExistError
from cms_backend.db.models import Book

router = APIRouter(prefix="/libraries", tags=["libraries"])
router = APIRouter(prefix="/collections", tags=["collections"])


def _build_library_xml(books: list[Book]) -> str:
Expand Down Expand Up @@ -67,33 +67,33 @@ def _build_library_xml(books: list[Book]) -> str:
return ET.tostring(library_elem, encoding="unicode")


@router.get("/{library_id_or_name}/catalog.xml")
@router.get("/{collection_id_or_name}/catalog.xml")
async def get_library_catalog_xml(
library_id_or_name: Annotated[str, Path()],
collection_id_or_name: Annotated[str, Path()],
session: Annotated[OrmSession, Depends(gen_dbsession)],
):
"""Get library catalog as XML. Library can be specified by ID (UUID) or name."""
"""Get collection catalog as XML library by collection ID (UUID) or name."""
# Try to parse as UUID first, otherwise treat as name
library = None
collection = None
try:
library_id = UUID(library_id_or_name)
collection_id = UUID(collection_id_or_name)
try:
library = get_library(session, library_id)
collection = get_collection(session, collection_id)
except RecordDoesNotExistError:
pass
except ValueError:
# Not a valid UUID, try as name
library = get_library_by_name_or_none(session, library_id_or_name)
collection = get_collection_by_name_or_none(session, collection_id_or_name)

if library is None:
if collection is None:
return Response(
content='<?xml version="1.0" encoding="UTF-8"?>'
'<library version="20110515"></library>',
status_code=HTTPStatus.NOT_FOUND,
media_type="application/xml",
)

books = get_latest_books_for_library(session, library.id)
books = get_latest_books_for_collection(session, collection.id)
xml_content = _build_library_xml(books)

return Response(
Expand Down
49 changes: 13 additions & 36 deletions backend/src/cms_backend/api/routes/titles.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
from cms_backend.schemas import BaseModel
from cms_backend.schemas.orms import (
BookLightSchema,
TitleCollectionSchema,
TitleFullSchema,
TitleLightSchema,
WarehousePathInfoSchema,
)

router = APIRouter(prefix="/titles", tags=["titles"])
Expand All @@ -29,12 +29,7 @@ class TitlesGetSchema(BaseModel):

class TitleCreateSchema(BaseModel):
name: str
producer_unique_id: str
producer_display_name: str | None = None
producer_display_url: str | None = None
dev_warehouse_path_ids: list[UUID]
prod_warehouse_path_ids: list[UUID]
in_prod: bool = False
maturity: str | None = None


@router.get("")
Expand Down Expand Up @@ -67,29 +62,10 @@ def get_title(
"""Get a title by ID with full details including books"""
title = db_get_title_by_id(session, title_id=title_id)

# Build sorted warehouse path lists by path_type
def build_warehouse_paths(path_type: str) -> list[WarehousePathInfoSchema]:
paths = [
WarehousePathInfoSchema(
path_id=twp.warehouse_path.id,
folder_name=twp.warehouse_path.folder_name,
warehouse_name=twp.warehouse_path.warehouse.name,
)
for twp in title.warehouse_paths
if twp.path_type == path_type
]
# Sort alphabetically by warehouse_name then folder_name
return sorted(paths, key=lambda p: (p.warehouse_name, p.folder_name))

return TitleFullSchema(
id=title.id,
name=title.name,
producer_unique_id=title.producer_unique_id,
producer_display_name=title.producer_display_name,
producer_display_url=title.producer_display_url,
dev_warehouse_paths=build_warehouse_paths("dev"),
prod_warehouse_paths=build_warehouse_paths("prod"),
in_prod=title.in_prod,
maturity=title.maturity,
events=title.events,
books=[
BookLightSchema(
Expand All @@ -103,6 +79,14 @@ def build_warehouse_paths(path_type: str) -> list[WarehousePathInfoSchema]:
)
for book in title.books
],
collections=[
TitleCollectionSchema(
collection_id=tc.collection_id,
collection_name=tc.collection.name,
path=str(tc.path),
)
for tc in title.collections
],
)


Expand All @@ -115,17 +99,10 @@ def create_title(
title = db_create_title(
session,
name=title_data.name,
producer_unique_id=title_data.producer_unique_id,
producer_display_name=title_data.producer_display_name,
producer_display_url=title_data.producer_display_url,
dev_warehouse_path_ids=title_data.dev_warehouse_path_ids,
prod_warehouse_path_ids=title_data.prod_warehouse_path_ids,
in_prod=title_data.in_prod,
maturity=title_data.maturity,
)
return TitleLightSchema(
id=title.id,
name=title.name,
producer_unique_id=title.producer_unique_id,
producer_display_name=title.producer_display_name,
producer_display_url=title.producer_display_url,
maturity=title.maturity,
)
19 changes: 0 additions & 19 deletions backend/src/cms_backend/api/routes/warehouse_paths.py

This file was deleted.

31 changes: 14 additions & 17 deletions backend/src/cms_backend/db/book.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from pathlib import Path
from typing import Any
from uuid import UUID

from sqlalchemy import select
from sqlalchemy.orm import Session as OrmSession

from cms_backend.db.models import Book, BookLocation, WarehousePath, ZimfarmNotification
from cms_backend.db.models import Book, BookLocation, Warehouse, ZimfarmNotification
from cms_backend.utils.datetime import getnow


Expand All @@ -18,9 +19,6 @@ def create_book(
zim_metadata: dict[str, Any],
zimcheck_result: dict[str, Any],
zimfarm_notification: ZimfarmNotification,
producer_display_name: str,
producer_display_url: str,
producer_unique_id: str,
) -> Book:
"""Create a new book"""

Expand All @@ -41,9 +39,6 @@ def create_book(
date=date,
flavour=flavour,
zimfarm_notification=zimfarm_notification,
producer_display_name=producer_display_name,
producer_display_url=producer_display_url,
producer_unique_id=producer_unique_id,
)
session.add(book)
zimfarm_notification.events.append(
Expand All @@ -60,7 +55,8 @@ def create_book_location(
session: OrmSession,
*,
book: Book,
warehouse_path_id: UUID,
warehouse_id: UUID,
path: Path,
filename: str,
status: str = "current",
) -> BookLocation:
Expand All @@ -69,32 +65,33 @@ def create_book_location(
Args:
session: SQLAlchemy session
book: Book instance
warehouse_path_id: ID of the warehouse path
warehouse_id: ID of the warehouse
path: Folder path within the warehouse (e.g., "dev-zim")
filename: Filename in warehouse
status: Location status ('current' or 'target'), defaults to 'current'

Returns:
Created BookLocation instance
"""
# Get warehouse path info for event message
warehouse_path = session.get(WarehousePath, warehouse_path_id)
if not warehouse_path:
raise ValueError(f"WarehousePath with id {warehouse_path_id} not found")
# Get warehouse info for event message
warehouse = session.get(Warehouse, warehouse_id)
if not warehouse:
raise ValueError(f"Warehouse with id {warehouse_id} not found")

warehouse_name = warehouse_path.warehouse.name
folder_name = warehouse_path.folder_name
warehouse_name = warehouse.name

location = BookLocation(
book_id=book.id,
warehouse_id=warehouse_id,
path=path,
status=status,
filename=filename,
)
location.warehouse_path_id = warehouse_path_id
session.add(location)
book.locations.append(location)
book.events.append(
f"{getnow()}: added {status} location: {filename} in {warehouse_name}: "
f"{folder_name} ({warehouse_path_id})"
f"{path} ({warehouse_id})"
)

return location
Expand Down
Loading