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:
parent
89ef2751fa
commit
44e5905bd7
2 changed files with 44 additions and 0 deletions
|
|
@ -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 ─────────────────────────────────────────────────────
|
# ── Admin: Worker status ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
WORKER_STATUS_CACHE_KEY = "chrysopedia:worker_status"
|
WORKER_STATUS_CACHE_KEY = "chrysopedia:worker_status"
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ class CreatorRead(CreatorBase):
|
||||||
model_config = ConfigDict(from_attributes=True)
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
id: uuid.UUID
|
id: uuid.UUID
|
||||||
|
avatar_url: str | None = None
|
||||||
view_count: int = 0
|
view_count: int = 0
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
updated_at: datetime
|
updated_at: datetime
|
||||||
|
|
@ -288,6 +289,7 @@ class CreatorInfo(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
slug: str
|
slug: str
|
||||||
genres: list[str] | None = None
|
genres: list[str] | None = None
|
||||||
|
avatar_url: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class SourceVideoSummary(BaseModel):
|
class SourceVideoSummary(BaseModel):
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue