fix: Creators endpoint returns paginated response, review queue limit raised to 1000, added GET /review/moments/{id} endpoint
- Creators: response_model changed from list to {items, total, offset, limit} matching frontend CreatorBrowseResponse
- Review queue: limit raised from 100 to 1000
- New GET /review/moments/{moment_id} endpoint for direct moment fetch
- MomentDetail uses fetchMoment instead of fetching full queue
- Merge candidates fetch uses limit=100
This commit is contained in:
parent
0b0ca598b4
commit
76138887d2
4 changed files with 44 additions and 15 deletions
|
|
@ -20,14 +20,14 @@ logger = logging.getLogger("chrysopedia.creators")
|
|||
router = APIRouter(prefix="/creators", tags=["creators"])
|
||||
|
||||
|
||||
@router.get("", response_model=list[CreatorBrowseItem])
|
||||
@router.get("")
|
||||
async def list_creators(
|
||||
sort: Annotated[str, Query()] = "random",
|
||||
genre: Annotated[str | None, Query()] = None,
|
||||
offset: Annotated[int, Query(ge=0)] = 0,
|
||||
limit: Annotated[int, Query(ge=1, le=100)] = 50,
|
||||
db: AsyncSession = Depends(get_session),
|
||||
) -> list[CreatorBrowseItem]:
|
||||
):
|
||||
"""List creators with sort, genre filter, and technique/video counts.
|
||||
|
||||
- **sort**: ``random`` (default, R014 creator equity), ``alpha``, ``views``
|
||||
|
|
@ -80,11 +80,17 @@ async def list_creators(
|
|||
CreatorBrowseItem(**base.model_dump(), technique_count=tc, video_count=vc)
|
||||
)
|
||||
|
||||
# Get total count (without offset/limit)
|
||||
count_stmt = select(func.count()).select_from(Creator)
|
||||
if genre:
|
||||
count_stmt = count_stmt.where(Creator.genres.any(genre))
|
||||
total = (await db.execute(count_stmt)).scalar() or 0
|
||||
|
||||
logger.debug(
|
||||
"Listed %d creators (sort=%s, genre=%s, offset=%d, limit=%d)",
|
||||
len(items), sort, genre, offset, limit,
|
||||
)
|
||||
return items
|
||||
return {"items": items, "total": total, "offset": offset, "limit": limit}
|
||||
|
||||
|
||||
@router.get("/{slug}", response_model=CreatorDetail)
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ def _moment_to_queue_item(
|
|||
async def list_queue(
|
||||
status: Annotated[str, Query()] = "pending",
|
||||
offset: Annotated[int, Query(ge=0)] = 0,
|
||||
limit: Annotated[int, Query(ge=1, le=100)] = 50,
|
||||
limit: Annotated[int, Query(ge=1, le=1000)] = 50,
|
||||
db: AsyncSession = Depends(get_session),
|
||||
) -> ReviewQueueResponse:
|
||||
"""List key moments in the review queue, filtered by status."""
|
||||
|
|
@ -313,6 +313,27 @@ async def merge_moments(
|
|||
return KeyMomentRead.model_validate(source)
|
||||
|
||||
|
||||
|
||||
|
||||
@router.get("/moments/{moment_id}", response_model=ReviewQueueItem)
|
||||
async def get_moment(
|
||||
moment_id: uuid.UUID,
|
||||
db: AsyncSession = Depends(get_session),
|
||||
) -> ReviewQueueItem:
|
||||
"""Get a single key moment by ID with video and creator info."""
|
||||
stmt = (
|
||||
select(KeyMoment, SourceVideo.file_path, Creator.name)
|
||||
.join(SourceVideo, KeyMoment.source_video_id == SourceVideo.id)
|
||||
.join(Creator, SourceVideo.creator_id == Creator.id)
|
||||
.where(KeyMoment.id == moment_id)
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
row = result.one_or_none()
|
||||
if row is None:
|
||||
raise HTTPException(status_code=404, detail=f"Moment {moment_id} not found")
|
||||
moment, file_path, creator_name = row
|
||||
return _moment_to_queue_item(moment, file_path or "", creator_name)
|
||||
|
||||
@router.get("/mode", response_model=ReviewModeResponse)
|
||||
async def get_mode() -> ReviewModeResponse:
|
||||
"""Get the current review mode (review vs auto)."""
|
||||
|
|
|
|||
|
|
@ -121,6 +121,12 @@ export async function fetchQueue(
|
|||
);
|
||||
}
|
||||
|
||||
export async function fetchMoment(
|
||||
momentId: string,
|
||||
): Promise<ReviewQueueItem> {
|
||||
return request<ReviewQueueItem>(`${BASE}/moments/${momentId}`);
|
||||
}
|
||||
|
||||
export async function fetchStats(): Promise<ReviewStatsResponse> {
|
||||
return request<ReviewStatsResponse>(`${BASE}/stats`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useParams, useNavigate, Link } from "react-router-dom";
|
||||
import {
|
||||
fetchMoment,
|
||||
fetchQueue,
|
||||
approveMoment,
|
||||
rejectMoment,
|
||||
|
|
@ -59,16 +60,11 @@ export default function MomentDetail() {
|
|||
setError(null);
|
||||
try {
|
||||
// Fetch all moments and find the one matching our ID
|
||||
const res = await fetchQueue({ limit: 500 });
|
||||
const found = res.items.find((m) => m.id === momentId);
|
||||
if (!found) {
|
||||
setError("Moment not found");
|
||||
} else {
|
||||
const found = await fetchMoment(momentId);
|
||||
setMoment(found);
|
||||
setEditTitle(found.title);
|
||||
setEditSummary(found.summary);
|
||||
setEditContentType(found.content_type);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to load moment");
|
||||
} finally {
|
||||
|
|
@ -174,7 +170,7 @@ export default function MomentDetail() {
|
|||
setActionError(null);
|
||||
try {
|
||||
// Load moments from the same video for merge candidates
|
||||
const res = await fetchQueue({ limit: 500 });
|
||||
const res = await fetchQueue({ limit: 100 });
|
||||
const candidates = res.items.filter(
|
||||
(m) => m.source_video_id === moment.source_video_id && m.id !== moment.id
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue