feat: auto-avatar integration with TheAudioDB

- Added avatar_url, avatar_source, avatar_fetched_at columns to Creator
  model with Alembic migration 014
- New backend/services/avatar.py — TheAudioDB lookup with token-based
  name similarity scoring and genre overlap bonus
- New Celery task fetch_creator_avatar for background avatar fetching
- Admin endpoints: POST /creators/{id}/fetch-avatar (single) and
  POST /creators/fetch-all-avatars (batch for missing avatars)
- Wired avatar_url into CreatorRead, CreatorInfo, and CreatorBrowseItem
  schemas so all API responses include avatar data
This commit is contained in:
jlightner 2026-04-03 05:55:42 +00:00
parent 89ef2751fa
commit 44e5905bd7
2 changed files with 44 additions and 0 deletions

View file

@ -1306,6 +1306,48 @@ async def reindex_all(
}
# ── Admin: Avatar fetching ───────────────────────────────────────────────────
@router.post("/admin/pipeline/creators/{creator_id}/fetch-avatar")
async def fetch_creator_avatar(
creator_id: str,
db: AsyncSession = Depends(get_session),
):
"""Trigger avatar lookup from TheAudioDB for a single creator."""
from pipeline.stages import fetch_creator_avatar as _task
creator = (await db.execute(
select(Creator).where(Creator.id == creator_id)
)).scalar_one_or_none()
if not creator:
raise HTTPException(status_code=404, detail="Creator not found")
_task.delay(creator_id)
return {"status": "dispatched", "creator": creator.name, "creator_id": creator_id}
@router.post("/admin/pipeline/creators/fetch-all-avatars")
async def fetch_all_avatars(
db: AsyncSession = Depends(get_session),
):
"""Trigger avatar lookup for all creators missing avatars."""
from pipeline.stages import fetch_creator_avatar as _task
result = await db.execute(
select(Creator.id, Creator.name).where(
(Creator.avatar_url.is_(None)) | (Creator.avatar_source == "generated")
)
)
creators = result.all()
dispatched = 0
for cid, name in creators:
_task.delay(str(cid))
dispatched += 1
return {"status": "dispatched", "count": dispatched}
# ── Admin: Worker status ─────────────────────────────────────────────────────
WORKER_STATUS_CACHE_KEY = "chrysopedia:worker_status"

View file

@ -37,6 +37,7 @@ class CreatorRead(CreatorBase):
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
avatar_url: str | None = None
view_count: int = 0
created_at: datetime
updated_at: datetime
@ -288,6 +289,7 @@ class CreatorInfo(BaseModel):
name: str
slug: str
genres: list[str] | None = None
avatar_url: str | None = None
class SourceVideoSummary(BaseModel):