fix: scope book progress and shelves to the requesting user
This commit is contained in:
@@ -14,7 +14,7 @@ from advanced_alchemy.extensions.litestar.providers import (
|
|||||||
from advanced_alchemy.exceptions import NotFoundError
|
from advanced_alchemy.exceptions import NotFoundError
|
||||||
from advanced_alchemy.filters import CollectionFilter, StatementFilter
|
from advanced_alchemy.filters import CollectionFilter, StatementFilter
|
||||||
from advanced_alchemy.service import FilterTypeT
|
from advanced_alchemy.service import FilterTypeT
|
||||||
from sqlalchemy.orm import selectinload, with_loader_criteria
|
from sqlalchemy.orm import selectinload
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from litestar import Request
|
from litestar import Request
|
||||||
from litestar.params import Dependency, Parameter
|
from litestar.params import Dependency, Parameter
|
||||||
@@ -51,8 +51,19 @@ from chitai.services.filters.book import (
|
|||||||
|
|
||||||
|
|
||||||
async def provide_book_service(
|
async def provide_book_service(
|
||||||
db_session: AsyncSession, current_user: m.User | None = None
|
db_session: AsyncSession, current_user: m.User = Dependency(skip_validation=True)
|
||||||
) -> AsyncGenerator[BookService, None]:
|
) -> AsyncGenerator[BookService, None]:
|
||||||
|
"""
|
||||||
|
Provide a BookService with per-user data scoped to the caller.
|
||||||
|
|
||||||
|
`current_user` is a required dependency, not an optional argument. It used to
|
||||||
|
default to None with the scoping below wrapped in `if current_user:` — and
|
||||||
|
when it was not injected, that block was silently skipped, so
|
||||||
|
`Book.progress_records` and `Book.list_links` loaded *every* user's rows.
|
||||||
|
`Book.progress` returns `progress_records[0]`, so one user could see another
|
||||||
|
user's reading position; the shelf checkboxes leaked the same way. Failing
|
||||||
|
loudly on a missing user is the point of the change.
|
||||||
|
"""
|
||||||
load = [
|
load = [
|
||||||
selectinload(m.Book.author_links).selectinload(m.BookAuthorLink.author),
|
selectinload(m.Book.author_links).selectinload(m.BookAuthorLink.author),
|
||||||
selectinload(m.Book.tag_links).selectinload(m.BookTagLink.tag),
|
selectinload(m.Book.tag_links).selectinload(m.BookTagLink.tag),
|
||||||
@@ -60,31 +71,26 @@ async def provide_book_service(
|
|||||||
m.Book.files,
|
m.Book.files,
|
||||||
m.Book.identifiers,
|
m.Book.identifiers,
|
||||||
m.Book.series,
|
m.Book.series,
|
||||||
|
# Reading progress, restricted to the caller.
|
||||||
|
#
|
||||||
|
# The restriction lives on the relationship via .and_() rather than in a
|
||||||
|
# separate with_loader_criteria(). advanced_alchemy's
|
||||||
|
# get_abstract_loader_options() keeps only _AbstractLoad,
|
||||||
|
# InstrumentedAttribute, RelationshipProperty and "*" entries and drops
|
||||||
|
# everything else — and with_loader_criteria() is none of those, so the
|
||||||
|
# previous criteria were discarded before reaching a query. A
|
||||||
|
# selectinload() carrying its own .and_() survives that filter.
|
||||||
|
selectinload(
|
||||||
|
m.Book.progress_records.and_(m.BookProgress.user_id == current_user.id)
|
||||||
|
),
|
||||||
|
# Bookshelf membership, restricted to the caller
|
||||||
|
selectinload(
|
||||||
|
m.Book.list_links.and_(
|
||||||
|
m.BookListLink.book_list.has(m.BookList.user_id == current_user.id)
|
||||||
|
)
|
||||||
|
).selectinload(m.BookListLink.book_list),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Load in specific user-book data
|
|
||||||
if current_user:
|
|
||||||
# Load progress data
|
|
||||||
load.extend(
|
|
||||||
[
|
|
||||||
selectinload(m.Book.progress_records),
|
|
||||||
with_loader_criteria(
|
|
||||||
m.BookProgress, m.BookProgress.user_id == current_user.id
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Load shelf data
|
|
||||||
load.extend(
|
|
||||||
[
|
|
||||||
selectinload(m.Book.list_links).selectinload(m.BookListLink.book_list),
|
|
||||||
with_loader_criteria(
|
|
||||||
m.BookListLink,
|
|
||||||
m.Book.lists.any(m.BookList.user_id == current_user.id),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
provider_func = create_service_provider(
|
provider_func = create_service_provider(
|
||||||
BookService,
|
BookService,
|
||||||
load=load,
|
load=load,
|
||||||
|
|||||||
Reference in New Issue
Block a user