- Header shows a live sync status (total videos + channels still backfilling, or "paused"), polled every 30s - Admins can pause/resume the background sync; a paused flag in app_state makes scheduled jobs skip (migration 0006) - GET /api/feed/count returns the number of videos matching the current filters; shared filter builder keeps it in sync with /api/feed; shown above the feed - /api/sync/status reports backfill progress, pending enrichment and paused state
113 lines
3.5 KiB
Python
113 lines
3.5 KiB
Python
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy import func, select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app import quota, state
|
|
from app.auth import current_user
|
|
from app.db import get_db
|
|
from app.models import Channel, Subscription, User, Video
|
|
from app.sync.runner import run_enrich, run_recent_backfill, run_rss_poll
|
|
from app.sync.subscriptions import import_subscriptions
|
|
|
|
router = APIRouter(prefix="/api/sync", tags=["sync"])
|
|
|
|
|
|
def _user_channels(db: Session, user: User) -> list[Channel]:
|
|
return (
|
|
db.execute(
|
|
select(Channel)
|
|
.join(Subscription, Subscription.channel_id == Channel.id)
|
|
.where(Subscription.user_id == user.id)
|
|
)
|
|
.scalars()
|
|
.all()
|
|
)
|
|
|
|
|
|
@router.post("/subscriptions")
|
|
def sync_subscriptions(
|
|
user: User = Depends(current_user), db: Session = Depends(get_db)
|
|
) -> dict:
|
|
before = quota.units_used_today(db)
|
|
result = import_subscriptions(db, user)
|
|
result["quota_used_estimate"] = quota.units_used_today(db) - before
|
|
result["quota_remaining_today"] = quota.remaining_today(db)
|
|
return result
|
|
|
|
|
|
@router.post("/rss")
|
|
def sync_rss(
|
|
user: User = Depends(current_user), db: Session = Depends(get_db)
|
|
) -> dict:
|
|
new = run_rss_poll(db, _user_channels(db, user))
|
|
return {"new_videos": new}
|
|
|
|
|
|
@router.post("/backfill")
|
|
def sync_backfill(
|
|
user: User = Depends(current_user),
|
|
db: Session = Depends(get_db),
|
|
max_channels: int = 25,
|
|
) -> dict:
|
|
before = quota.units_used_today(db)
|
|
result = run_recent_backfill(db, _user_channels(db, user), max_channels=max_channels)
|
|
result["quota_used_estimate"] = quota.units_used_today(db) - before
|
|
return result
|
|
|
|
|
|
@router.post("/enrich")
|
|
def sync_enrich(
|
|
user: User = Depends(current_user), db: Session = Depends(get_db)
|
|
) -> dict:
|
|
before = quota.units_used_today(db)
|
|
enriched = run_enrich(db)
|
|
return {
|
|
"enriched": enriched,
|
|
"quota_used_estimate": quota.units_used_today(db) - before,
|
|
}
|
|
|
|
|
|
@router.get("/status")
|
|
def sync_status(
|
|
user: User = Depends(current_user), db: Session = Depends(get_db)
|
|
) -> dict:
|
|
sub_count = db.scalar(
|
|
select(func.count())
|
|
.select_from(Subscription)
|
|
.where(Subscription.user_id == user.id)
|
|
)
|
|
return {
|
|
"subscriptions": sub_count,
|
|
"channels_total": db.scalar(select(func.count()).select_from(Channel)),
|
|
"channels_backfilling": db.scalar(
|
|
select(func.count()).select_from(Channel).where(Channel.backfill_done.is_(False))
|
|
),
|
|
"videos_total": db.scalar(select(func.count()).select_from(Video)),
|
|
"pending_enrich": db.scalar(
|
|
select(func.count()).select_from(Video).where(Video.enriched_at.is_(None))
|
|
),
|
|
"quota_used_today": quota.units_used_today(db),
|
|
"quota_remaining_today": quota.remaining_today(db),
|
|
"paused": state.is_sync_paused(db),
|
|
"is_admin": user.role == "admin",
|
|
}
|
|
|
|
|
|
@router.post("/pause")
|
|
def pause_sync(
|
|
user: User = Depends(current_user), db: Session = Depends(get_db)
|
|
) -> dict:
|
|
if user.role != "admin":
|
|
raise HTTPException(status_code=403, detail="Admin only")
|
|
state.set_sync_paused(db, True)
|
|
return {"paused": True}
|
|
|
|
|
|
@router.post("/resume")
|
|
def resume_sync(
|
|
user: User = Depends(current_user), db: Session = Depends(get_db)
|
|
) -> dict:
|
|
if user.role != "admin":
|
|
raise HTTPException(status_code=403, detail="Admin only")
|
|
state.set_sync_paused(db, False)
|
|
return {"paused": False}
|