From 44e5905bd71b33a0e07998ccb65aea0f7b00985f Mon Sep 17 00:00:00 2001 From: jlightner Date: Fri, 3 Apr 2026 05:55:42 +0000 Subject: [PATCH] feat: auto-avatar integration with TheAudioDB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- backend/routers/pipeline.py | 42 +++++++++++++++++++++++++++++++++++++ backend/schemas.py | 2 ++ 2 files changed, 44 insertions(+) diff --git a/backend/routers/pipeline.py b/backend/routers/pipeline.py index bd478ec..d685631 100644 --- a/backend/routers/pipeline.py +++ b/backend/routers/pipeline.py @@ -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" diff --git a/backend/schemas.py b/backend/schemas.py index 54bf520..9f4d11d 100644 --- a/backend/schemas.py +++ b/backend/schemas.py @@ -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):