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 config import get_settings
|
||||||
from database import get_session
|
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 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")
|
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 ─────────────────────────────────────────────────────────
|
# ── Admin: Retrigger ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@router.post("/admin/pipeline/trigger/{video_id}")
|
@router.post("/admin/pipeline/trigger/{video_id}")
|
||||||
|
|
|
||||||
|
|
@ -432,3 +432,30 @@ class TokenSummaryResponse(BaseModel):
|
||||||
video_id: str
|
video_id: str
|
||||||
stages: list[TokenStageSummary] = Field(default_factory=list)
|
stages: list[TokenStageSummary] = Field(default_factory=list)
|
||||||
grand_total_tokens: int
|
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