feat: Added key_moment_count correlated subquery to technique list API…
- "backend/schemas.py" - "backend/routers/techniques.py" - "frontend/src/api/public-client.ts" - "frontend/src/pages/Home.tsx" - "frontend/src/App.css" GSD-Task: S03/T01
This commit is contained in:
parent
0c8bbb32d6
commit
80439d43cf
5 changed files with 50 additions and 10 deletions
|
|
@ -38,34 +38,53 @@ async def list_techniques(
|
|||
db: AsyncSession = Depends(get_session),
|
||||
) -> PaginatedResponse:
|
||||
"""List technique pages with optional category/creator filtering."""
|
||||
stmt = select(TechniquePage)
|
||||
# Correlated subquery for key moment count (same pattern as creators.py)
|
||||
key_moment_count_sq = (
|
||||
select(func.count())
|
||||
.where(KeyMoment.technique_page_id == TechniquePage.id)
|
||||
.correlate(TechniquePage)
|
||||
.scalar_subquery()
|
||||
)
|
||||
|
||||
# Build base query with filters
|
||||
base_stmt = select(TechniquePage.id)
|
||||
if category:
|
||||
stmt = stmt.where(TechniquePage.topic_category == category)
|
||||
|
||||
base_stmt = base_stmt.where(TechniquePage.topic_category == category)
|
||||
if creator_slug:
|
||||
# Join to Creator to filter by slug
|
||||
stmt = stmt.join(Creator, TechniquePage.creator_id == Creator.id).where(
|
||||
base_stmt = base_stmt.join(Creator, TechniquePage.creator_id == Creator.id).where(
|
||||
Creator.slug == creator_slug
|
||||
)
|
||||
|
||||
# Count total before pagination
|
||||
from sqlalchemy import func
|
||||
|
||||
count_stmt = select(func.count()).select_from(stmt.subquery())
|
||||
count_stmt = select(func.count()).select_from(base_stmt.subquery())
|
||||
count_result = await db.execute(count_stmt)
|
||||
total = count_result.scalar() or 0
|
||||
|
||||
# Main query with subquery column
|
||||
stmt = select(
|
||||
TechniquePage,
|
||||
key_moment_count_sq.label("key_moment_count"),
|
||||
)
|
||||
if category:
|
||||
stmt = stmt.where(TechniquePage.topic_category == category)
|
||||
if creator_slug:
|
||||
stmt = stmt.join(Creator, TechniquePage.creator_id == Creator.id).where(
|
||||
Creator.slug == creator_slug
|
||||
)
|
||||
|
||||
stmt = stmt.options(selectinload(TechniquePage.creator)).order_by(TechniquePage.created_at.desc()).offset(offset).limit(limit)
|
||||
result = await db.execute(stmt)
|
||||
pages = result.scalars().all()
|
||||
rows = result.all()
|
||||
|
||||
items = []
|
||||
for p in pages:
|
||||
for row in rows:
|
||||
p = row[0]
|
||||
km_count = row[1] or 0
|
||||
item = TechniquePageRead.model_validate(p)
|
||||
if p.creator:
|
||||
item.creator_name = p.creator.name
|
||||
item.creator_slug = p.creator.slug
|
||||
item.key_moment_count = km_count
|
||||
items.append(item)
|
||||
|
||||
return PaginatedResponse(
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ class TechniquePageRead(TechniquePageBase):
|
|||
creator_slug: str = ""
|
||||
source_quality: str | None = None
|
||||
view_count: int = 0
|
||||
key_moment_count: int = 0
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
|
|
|||
|
|
@ -1110,6 +1110,17 @@ a.app-footer__repo:hover {
|
|||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.recent-card__moments {
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-text-tertiary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pill--tag {
|
||||
font-size: 0.625rem;
|
||||
padding: 0 0.375rem;
|
||||
}
|
||||
|
||||
/* ── Search results page ──────────────────────────────────────────────────── */
|
||||
|
||||
.search-results-page {
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ export interface TechniqueListItem {
|
|||
creator_slug: string;
|
||||
source_quality: string | null;
|
||||
view_count: number;
|
||||
key_moment_count: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -210,6 +210,9 @@ export default function Home() {
|
|||
<span className="badge badge--category">
|
||||
{t.topic_category}
|
||||
</span>
|
||||
{t.topic_tags && t.topic_tags.length > 0 && t.topic_tags.map(tag => (
|
||||
<span key={tag} className="pill pill--tag">{tag}</span>
|
||||
))}
|
||||
{t.summary && (
|
||||
<span className="recent-card__summary">
|
||||
{t.summary.length > 100
|
||||
|
|
@ -217,6 +220,11 @@ export default function Home() {
|
|||
: t.summary}
|
||||
</span>
|
||||
)}
|
||||
{t.key_moment_count > 0 && (
|
||||
<span className="recent-card__moments">
|
||||
{t.key_moment_count} moment{t.key_moment_count !== 1 ? 's' : ''}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</Link>
|
||||
))}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue