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),
|
db: AsyncSession = Depends(get_session),
|
||||||
) -> PaginatedResponse:
|
) -> PaginatedResponse:
|
||||||
"""List technique pages with optional category/creator filtering."""
|
"""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:
|
if category:
|
||||||
stmt = stmt.where(TechniquePage.topic_category == category)
|
base_stmt = base_stmt.where(TechniquePage.topic_category == category)
|
||||||
|
|
||||||
if creator_slug:
|
if creator_slug:
|
||||||
# Join to Creator to filter by slug
|
base_stmt = base_stmt.join(Creator, TechniquePage.creator_id == Creator.id).where(
|
||||||
stmt = stmt.join(Creator, TechniquePage.creator_id == Creator.id).where(
|
|
||||||
Creator.slug == creator_slug
|
Creator.slug == creator_slug
|
||||||
)
|
)
|
||||||
|
|
||||||
# Count total before pagination
|
# Count total before pagination
|
||||||
from sqlalchemy import func
|
count_stmt = select(func.count()).select_from(base_stmt.subquery())
|
||||||
|
|
||||||
count_stmt = select(func.count()).select_from(stmt.subquery())
|
|
||||||
count_result = await db.execute(count_stmt)
|
count_result = await db.execute(count_stmt)
|
||||||
total = count_result.scalar() or 0
|
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)
|
stmt = stmt.options(selectinload(TechniquePage.creator)).order_by(TechniquePage.created_at.desc()).offset(offset).limit(limit)
|
||||||
result = await db.execute(stmt)
|
result = await db.execute(stmt)
|
||||||
pages = result.scalars().all()
|
rows = result.all()
|
||||||
|
|
||||||
items = []
|
items = []
|
||||||
for p in pages:
|
for row in rows:
|
||||||
|
p = row[0]
|
||||||
|
km_count = row[1] or 0
|
||||||
item = TechniquePageRead.model_validate(p)
|
item = TechniquePageRead.model_validate(p)
|
||||||
if p.creator:
|
if p.creator:
|
||||||
item.creator_name = p.creator.name
|
item.creator_name = p.creator.name
|
||||||
item.creator_slug = p.creator.slug
|
item.creator_slug = p.creator.slug
|
||||||
|
item.key_moment_count = km_count
|
||||||
items.append(item)
|
items.append(item)
|
||||||
|
|
||||||
return PaginatedResponse(
|
return PaginatedResponse(
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,7 @@ class TechniquePageRead(TechniquePageBase):
|
||||||
creator_slug: str = ""
|
creator_slug: str = ""
|
||||||
source_quality: str | None = None
|
source_quality: str | None = None
|
||||||
view_count: int = 0
|
view_count: int = 0
|
||||||
|
key_moment_count: int = 0
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
updated_at: datetime
|
updated_at: datetime
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1110,6 +1110,17 @@ a.app-footer__repo:hover {
|
||||||
line-height: 1.4;
|
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 ──────────────────────────────────────────────────── */
|
||||||
|
|
||||||
.search-results-page {
|
.search-results-page {
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,7 @@ export interface TechniqueListItem {
|
||||||
creator_slug: string;
|
creator_slug: string;
|
||||||
source_quality: string | null;
|
source_quality: string | null;
|
||||||
view_count: number;
|
view_count: number;
|
||||||
|
key_moment_count: number;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -210,6 +210,9 @@ export default function Home() {
|
||||||
<span className="badge badge--category">
|
<span className="badge badge--category">
|
||||||
{t.topic_category}
|
{t.topic_category}
|
||||||
</span>
|
</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 && (
|
{t.summary && (
|
||||||
<span className="recent-card__summary">
|
<span className="recent-card__summary">
|
||||||
{t.summary.length > 100
|
{t.summary.length > 100
|
||||||
|
|
@ -217,6 +220,11 @@ export default function Home() {
|
||||||
: t.summary}
|
: t.summary}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
{t.key_moment_count > 0 && (
|
||||||
|
<span className="recent-card__moments">
|
||||||
|
{t.key_moment_count} moment{t.key_moment_count !== 1 ? 's' : ''}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue