feat: expose bracket results and discovery feed
Verify and deploy API / verify-deploy (push) Successful in 1m47s

This commit is contained in:
2026-09-19 04:10:38 +08:00
parent b152c04531
commit ed117301f2
3 changed files with 57 additions and 5 deletions
+38 -3
View File
@@ -16,6 +16,7 @@ from .schemas import (
LinkStartOut,
LoginRequest,
LoginResponse,
PublicRunOut,
RunOut,
TestCard,
TestDetail,
@@ -88,7 +89,8 @@ def list_tests(db: Session = Depends(get_db)) -> list[TestCard]:
slug=test.slug,
title=test.title,
subtitle=test.subtitle,
emoji=test.emoji,
description=test.subtitle,
cover_url=f"/assets/test-covers/{test.slug}.jpg",
accent=test.accent,
candidate_count=len(test.candidates),
run_count=counts.get(test.id, 0),
@@ -111,12 +113,19 @@ def get_test(slug: str, db: Session = Depends(get_db)) -> TestDetail:
slug=test.slug,
title=test.title,
subtitle=test.subtitle,
emoji=test.emoji,
description=test.subtitle,
cover_url=f"/assets/test-covers/{test.slug}.jpg",
accent=test.accent,
candidate_count=len(test.candidates),
run_count=run_count,
candidates=[
{"id": c.id, "name": c.name, "emoji": c.emoji, "seed_order": c.seed_order}
{
"id": c.id,
"name": c.name,
"description": None,
"image_url": None,
"seed_order": c.seed_order,
}
for c in test.candidates
],
)
@@ -132,10 +141,36 @@ def _run_out(run: TestRun) -> RunOut:
champion_name=run.champion.name,
runner_up_name=run.runner_up.name if run.runner_up else None,
ranking_names=[candidate_by_id[item] for item in run.ranking_ids if item in candidate_by_id],
decisions=run.decisions,
bracket_size=run.bracket_size,
candidate_names=candidate_by_id,
completed_at=run.completed_at,
)
@router.get("/results/discover", response_model=list[PublicRunOut])
def discover_results(
limit: int = Query(default=30, ge=1, le=100),
db: Session = Depends(get_db),
) -> list[PublicRunOut]:
runs = db.scalars(
select(TestRun)
.options(selectinload(TestRun.test), selectinload(TestRun.champion))
.order_by(TestRun.completed_at.desc())
.limit(limit)
).all()
return [
PublicRunOut(
id=run.id,
test_slug=run.test.slug,
test_title=run.test.title,
champion_name=run.champion.name,
completed_at=run.completed_at,
)
for run in runs
]
@router.post("/tests/{slug}/runs", response_model=RunOut, status_code=201)
def complete_run(
slug: str,
+15 -2
View File
@@ -25,7 +25,8 @@ class LoginResponse(BaseModel):
class CandidateOut(BaseModel):
id: str
name: str
emoji: str | None
description: str | None = None
image_url: str | None = None
seed_order: int
@@ -33,7 +34,8 @@ class TestCard(BaseModel):
slug: str
title: str
subtitle: str
emoji: str
description: str
cover_url: str
accent: str
candidate_count: int
run_count: int
@@ -66,12 +68,23 @@ class RunOut(BaseModel):
champion_name: str
runner_up_name: str | None
ranking_names: list[str]
decisions: list[DecisionIn]
bracket_size: int
candidate_names: dict[str, str]
completed_at: datetime
share_templates: list[str] = Field(
default_factory=lambda: ["celebration", "editorial", "sticker"]
)
class PublicRunOut(BaseModel):
id: str
test_slug: str
test_title: str
champion_name: str
completed_at: datetime
class LinkStartOut(BaseModel):
code: str
expires_at: datetime