feat: Added paginated GET /admin/pipeline/technique-pages endpoint with…
- "backend/routers/pipeline.py" - "backend/schemas.py" GSD-Task: S06/T01
This commit is contained in:
parent
304f3bc069
commit
bd8a928c95
2 changed files with 131 additions and 2 deletions
|
|
@ -22,9 +22,9 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||
|
||||
from config import get_settings
|
||||
from database import get_session
|
||||
from models import PipelineEvent, PipelineRun, PipelineRunStatus, SourceVideo, Creator, KeyMoment, TranscriptSegment, ProcessingStatus
|
||||
from models import PipelineEvent, PipelineRun, PipelineRunStatus, SourceVideo, Creator, KeyMoment, TranscriptSegment, ProcessingStatus, TechniquePage, TechniquePageVideo, TechniquePageVersion
|
||||
from redis_client import get_redis
|
||||
from schemas import DebugModeResponse, DebugModeUpdate, TokenStageSummary, TokenSummaryResponse
|
||||
from schemas import DebugModeResponse, DebugModeUpdate, TokenStageSummary, TokenSummaryResponse, AdminTechniquePageItem, AdminTechniquePageListResponse
|
||||
|
||||
logger = logging.getLogger("chrysopedia.pipeline")
|
||||
|
||||
|
|
@ -188,6 +188,108 @@ async def list_pipeline_videos(
|
|||
}
|
||||
|
||||
|
||||
# ── Admin: Technique Pages ───────────────────────────────────────────────────
|
||||
|
||||
@router.get(
|
||||
"/admin/pipeline/technique-pages",
|
||||
response_model=AdminTechniquePageListResponse,
|
||||
)
|
||||
async def list_admin_technique_pages(
|
||||
multi_source_only: bool = False,
|
||||
creator: Annotated[str | None, Query(description="Filter by creator slug")] = None,
|
||||
sort: Annotated[str, Query(description="Sort: recent, alpha, creator")] = "recent",
|
||||
offset: Annotated[int, Query(ge=0)] = 0,
|
||||
limit: Annotated[int, Query(ge=1, le=200)] = 50,
|
||||
db: AsyncSession = Depends(get_session),
|
||||
):
|
||||
"""List technique pages with source video counts, version counts, and creator info.
|
||||
|
||||
Supports filtering by multi-source pages only and by creator slug.
|
||||
"""
|
||||
# Correlated subquery: source video count per page
|
||||
video_count_sq = (
|
||||
select(func.count())
|
||||
.select_from(TechniquePageVideo)
|
||||
.where(TechniquePageVideo.technique_page_id == TechniquePage.id)
|
||||
.correlate(TechniquePage)
|
||||
.scalar_subquery()
|
||||
.label("source_video_count")
|
||||
)
|
||||
|
||||
# Correlated subquery: version count per page
|
||||
version_count_sq = (
|
||||
select(func.count())
|
||||
.select_from(TechniquePageVersion)
|
||||
.where(TechniquePageVersion.technique_page_id == TechniquePage.id)
|
||||
.correlate(TechniquePage)
|
||||
.scalar_subquery()
|
||||
.label("version_count")
|
||||
)
|
||||
|
||||
stmt = (
|
||||
select(
|
||||
TechniquePage.id,
|
||||
TechniquePage.title,
|
||||
TechniquePage.slug,
|
||||
TechniquePage.topic_category,
|
||||
TechniquePage.body_sections_format,
|
||||
TechniquePage.created_at,
|
||||
TechniquePage.updated_at,
|
||||
Creator.name.label("creator_name"),
|
||||
Creator.slug.label("creator_slug"),
|
||||
video_count_sq,
|
||||
version_count_sq,
|
||||
)
|
||||
.join(Creator, TechniquePage.creator_id == Creator.id)
|
||||
)
|
||||
|
||||
# Filters
|
||||
if multi_source_only:
|
||||
stmt = stmt.where(video_count_sq > 1)
|
||||
if creator:
|
||||
stmt = stmt.where(Creator.slug == creator)
|
||||
|
||||
# Count total before pagination
|
||||
count_stmt = select(func.count()).select_from(stmt.subquery())
|
||||
total = (await db.execute(count_stmt)).scalar() or 0
|
||||
|
||||
# Sort
|
||||
if sort == "alpha":
|
||||
stmt = stmt.order_by(TechniquePage.title.asc())
|
||||
elif sort == "creator":
|
||||
stmt = stmt.order_by(Creator.name.asc(), TechniquePage.title.asc())
|
||||
else: # "recent" default
|
||||
stmt = stmt.order_by(TechniquePage.updated_at.desc())
|
||||
|
||||
stmt = stmt.offset(offset).limit(limit)
|
||||
result = await db.execute(stmt)
|
||||
rows = result.all()
|
||||
|
||||
items = [
|
||||
AdminTechniquePageItem(
|
||||
id=r.id,
|
||||
title=r.title,
|
||||
slug=r.slug,
|
||||
creator_name=r.creator_name,
|
||||
creator_slug=r.creator_slug,
|
||||
topic_category=r.topic_category,
|
||||
body_sections_format=r.body_sections_format,
|
||||
source_video_count=r.source_video_count or 0,
|
||||
version_count=r.version_count or 0,
|
||||
created_at=r.created_at,
|
||||
updated_at=r.updated_at,
|
||||
)
|
||||
for r in rows
|
||||
]
|
||||
|
||||
return AdminTechniquePageListResponse(
|
||||
items=items,
|
||||
total=total,
|
||||
offset=offset,
|
||||
limit=limit,
|
||||
)
|
||||
|
||||
|
||||
# ── Admin: Retrigger ─────────────────────────────────────────────────────────
|
||||
|
||||
@router.post("/admin/pipeline/trigger/{video_id}")
|
||||
|
|
|
|||
|
|
@ -432,3 +432,30 @@ class TokenSummaryResponse(BaseModel):
|
|||
video_id: str
|
||||
stages: list[TokenStageSummary] = Field(default_factory=list)
|
||||
grand_total_tokens: int
|
||||
|
||||
|
||||
# ── Admin: Technique Pages ───────────────────────────────────────────────────
|
||||
|
||||
class AdminTechniquePageItem(BaseModel):
|
||||
"""Technique page with aggregated source/version counts for admin view."""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: uuid.UUID
|
||||
title: str
|
||||
slug: str
|
||||
creator_name: str
|
||||
creator_slug: str
|
||||
topic_category: str
|
||||
body_sections_format: str
|
||||
source_video_count: int = 0
|
||||
version_count: int = 0
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class AdminTechniquePageListResponse(BaseModel):
|
||||
"""Paginated list of technique pages for admin view."""
|
||||
items: list[AdminTechniquePageItem] = Field(default_factory=list)
|
||||
total: int = 0
|
||||
offset: int = 0
|
||||
limit: int = 50
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue