Channel features: subscribers, new-videos badges, activity-driven sync
- Store subscriber count from YouTube statistics and show it on channel page - Sync 50 videos per channel with playlistItems pagination support - Show per-channel and per-category new-videos counters (2-day window) - Replace hourly videos sync with activity trigger (2h idle) and incremental backfill (hard cap 200 per channel) - Clicking the sidebar new-videos count filters the category feed to recent videos only (new_only) - Update agent-team docs: deploy after green checks
This commit is contained in:
parent
fde9a439df
commit
e10df8dcbd
27 changed files with 1420 additions and 107 deletions
|
|
@ -1,14 +1,19 @@
|
|||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.config import settings
|
||||
from app.core.auth_dependency import require_session
|
||||
from app.core.slugify import unique_slugify
|
||||
from app.db import get_db
|
||||
from app.models.category import Category
|
||||
from app.models.channel import Channel
|
||||
from app.models.channel_category import channel_categories
|
||||
from app.models.video import Video
|
||||
|
||||
router = APIRouter(dependencies=[Depends(require_session)])
|
||||
|
||||
|
|
@ -40,13 +45,15 @@ def _name_taken(db: Session, name: str, exclude_id: int | None = None) -> bool:
|
|||
return any(row[0].casefold() == target for row in query.all())
|
||||
|
||||
|
||||
def _serialize(db: Session, category: Category, counts: dict[int, int]) -> dict:
|
||||
def _serialize(db: Session, category: Category, counts: dict[int, int], new_videos: dict[int, int] | None = None) -> dict:
|
||||
new_videos = new_videos or {}
|
||||
return {
|
||||
"id": category.id,
|
||||
"name": category.name,
|
||||
"slug": category.slug,
|
||||
"sort_order": category.sort_order,
|
||||
"channel_count": counts.get(category.id, 0),
|
||||
"new_videos_count": new_videos.get(category.id, 0),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -59,7 +66,17 @@ def list_categories(db: Session = Depends(get_db)) -> list[dict]:
|
|||
.all()
|
||||
)
|
||||
counts = dict(count_rows)
|
||||
return [_serialize(db, c, counts) for c in categories]
|
||||
since = datetime.now(timezone.utc) - timedelta(days=settings.new_videos_window_days)
|
||||
new_videos_rows = (
|
||||
db.query(channel_categories.c.category_id, func.count(Video.id))
|
||||
.join(Channel, Channel.id == channel_categories.c.channel_id)
|
||||
.join(Video, Video.channel_id == Channel.id)
|
||||
.filter(Channel.subscribed.is_(True), Video.published_at >= since)
|
||||
.group_by(channel_categories.c.category_id)
|
||||
.all()
|
||||
)
|
||||
new_videos = dict(new_videos_rows)
|
||||
return [_serialize(db, c, counts, new_videos) for c in categories]
|
||||
|
||||
|
||||
@router.post("/categories", status_code=201)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue