|
31 | 31 | ) |
32 | 32 | from app.services.cover_import import import_cover_from_url, is_external_cover_url |
33 | 33 | from app.services.quote_cache import get_or_fetch_dashboard_quote |
34 | | -from app.services.tags import build_book_read, cleanup_orphan_tags, sync_book_tags |
| 34 | +from app.services.tags import build_book_read, cleanup_orphan_tags, load_tags_batch, sync_book_tags |
35 | 35 | from app.time_utils import utcnow |
36 | 36 |
|
37 | 37 | logger = logging.getLogger(__name__) |
@@ -128,6 +128,14 @@ def _raise_integrity_conflict(exc: IntegrityError) -> None: |
128 | 128 | raise |
129 | 129 |
|
130 | 130 |
|
| 131 | +def _build_book_read_with_tags(book: Book, tags_text: str | None) -> BookRead: |
| 132 | + """Build a BookRead from a Book model with a pre-resolved tags string.""" |
| 133 | + payload = book.model_dump() |
| 134 | + payload.pop("user_id", None) |
| 135 | + payload["tags"] = tags_text |
| 136 | + return BookRead.model_validate(payload) |
| 137 | + |
| 138 | + |
131 | 139 | @router.get("", response_model=BookListResponse) |
132 | 140 | def list_books( |
133 | 141 | status: Optional[ReadingStatus] = Query(default=None), |
@@ -206,8 +214,13 @@ def list_books( |
206 | 214 |
|
207 | 215 | books = list(session.exec(statement).all()) |
208 | 216 | logger.debug("list_books — returning %d/%d book(s)", len(books), total) |
| 217 | + book_ids = [b.id for b in books if b.id is not None] |
| 218 | + book_tags_map = load_tags_batch(session, book_ids) if book_ids else {} |
209 | 219 | return BookListResponse( |
210 | | - books=[build_book_read(session, book) for book in books], |
| 220 | + books=[ |
| 221 | + _build_book_read_with_tags(book, book_tags_map.get(book.id)) |
| 222 | + for book in books |
| 223 | + ], |
211 | 224 | total=total, |
212 | 225 | ) |
213 | 226 |
|
@@ -279,12 +292,13 @@ def get_tag_cloud( |
279 | 292 | session: Session = Depends(get_session), |
280 | 293 | ) -> List[TagCloudEntry]: |
281 | 294 | """Return tags sorted by usage count (descending) for the authenticated user.""" |
| 295 | + count_label = func.count(BookTag.book_id).label("cnt") |
282 | 296 | rows = session.exec( |
283 | | - select(Tag.name, func.count(BookTag.book_id)) |
| 297 | + select(Tag.name, count_label) |
284 | 298 | .join(BookTag, BookTag.tag_id == Tag.id) |
285 | 299 | .where(Tag.user_id == current_user.id) |
286 | | - .group_by(Tag.id, Tag.name) |
287 | | - .order_by(func.count(BookTag.book_id).desc(), Tag.name.asc()) |
| 300 | + .group_by(Tag.id) |
| 301 | + .order_by(count_label.desc(), Tag.name.asc()) |
288 | 302 | .limit(limit) |
289 | 303 | ).all() |
290 | 304 | return [TagCloudEntry(tag=name, count=count) for name, count in rows] |
|
0 commit comments