chore(feed): Phase 2 #2 backend cleanup — dead return, dup helpers, shared const
- feed.py _filtered_query: drop the dead 2nd return element (status_expr was
used only internally for WHERE filters; all 3 callers discarded it as _status).
Now returns (query, rank_expr); fixed the stale docstring.
- youtube/client.py: extract _iter_playlist_items() — iter_my_playlist_video_ids
and iter_playlist_items_with_ids were near-identical playlistItems paging loops
(jscpd [173-187]≈[267-281]); both now map over the shared generator.
- sync/videos.py: extract _apply_video_batch() with an on_missing callback —
enrich_pending and refresh_live shared the fetch-map-apply skeleton, differing
only in the query and how they retire rows YouTube omits.
- models.py: add LIVE_OR_UPCOMING = ("live","upcoming"); replace the 4 duplicated
copies (feed.HIDDEN_LIVE, search._LIVE_HIDDEN, videos.py + channels.py inline).
Behavior-neutral. ruff clean on touched files, localdev boots, feed/worker healthy.
This commit is contained in:
parent
477056bfa8
commit
505f0e5650
6 changed files with 75 additions and 71 deletions
|
|
@ -12,6 +12,7 @@ from app import quota, sysconfig
|
|||
from app.auth import current_user, has_write_scope, require_human
|
||||
from app.db import get_db
|
||||
from app.models import (
|
||||
LIVE_OR_UPCOMING,
|
||||
BlockedChannel,
|
||||
Channel,
|
||||
ChannelTag,
|
||||
|
|
@ -59,7 +60,7 @@ def list_channels(
|
|||
func.max(Video.published_at),
|
||||
func.coalesce(func.sum(Video.duration_seconds), 0),
|
||||
func.count(Video.id).filter(Video.is_short.is_(True)),
|
||||
func.count(Video.id).filter(Video.live_status.in_(("live", "upcoming"))),
|
||||
func.count(Video.id).filter(Video.live_status.in_(LIVE_OR_UPCOMING)),
|
||||
)
|
||||
.where(Video.channel_id.in_(channel_ids))
|
||||
.group_by(Video.channel_id)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from app import quota
|
|||
from app.auth import current_user
|
||||
from app.db import get_db
|
||||
from app.models import (
|
||||
LIVE_OR_UPCOMING,
|
||||
BlockedChannel,
|
||||
Channel,
|
||||
ChannelTag,
|
||||
|
|
@ -34,7 +35,6 @@ router = APIRouter(prefix="/api", tags=["feed"])
|
|||
|
||||
# "saved" used to be a status; it's now membership in the built-in Watch later playlist.
|
||||
VALID_STATES = {"new", "watched", "hidden"}
|
||||
HIDDEN_LIVE = ("live", "upcoming")
|
||||
|
||||
# Resume-position thresholds (mirror the client): positions below this are "didn't
|
||||
# really start" and within this of the end are "basically finished" — neither is worth
|
||||
|
|
@ -132,7 +132,7 @@ def _filtered_query(
|
|||
exclude_tag_category: str | None = None,
|
||||
) -> tuple[Select, object]:
|
||||
"""Build the feed query (joins + all WHERE filters), shared by /feed and /feed/count.
|
||||
Returns the column-bearing select plus the watch-status expression for sorting.
|
||||
Returns the column-bearing select plus the priority/relevance rank expression for sorting.
|
||||
|
||||
`scope="my"` (default) restricts the feed to the user's own non-hidden subscriptions.
|
||||
`scope="all"` shows every video in the shared catalog (any user's ingested channels);
|
||||
|
|
@ -313,12 +313,12 @@ def _filtered_query(
|
|||
type_clauses = []
|
||||
if show_normal:
|
||||
type_clauses.append(
|
||||
and_(Video.is_short.is_(False), Video.live_status.notin_(HIDDEN_LIVE))
|
||||
and_(Video.is_short.is_(False), Video.live_status.notin_(LIVE_OR_UPCOMING))
|
||||
)
|
||||
if include_shorts:
|
||||
type_clauses.append(Video.is_short.is_(True))
|
||||
if include_live:
|
||||
type_clauses.append(Video.live_status.in_(HIDDEN_LIVE))
|
||||
type_clauses.append(Video.live_status.in_(LIVE_OR_UPCOMING))
|
||||
query = query.where(or_(*type_clauses) if type_clauses else false())
|
||||
|
||||
if show == "unwatched":
|
||||
|
|
@ -335,7 +335,7 @@ def _filtered_query(
|
|||
else: # all
|
||||
query = query.where(status_expr != "hidden")
|
||||
|
||||
return query, status_expr, rank_expr
|
||||
return query, rank_expr
|
||||
|
||||
|
||||
def _to_tsquery_str(q: str) -> str | None:
|
||||
|
|
@ -492,7 +492,7 @@ def get_feed(
|
|||
user: User = Depends(current_user),
|
||||
db: Session = Depends(get_db),
|
||||
) -> dict:
|
||||
query, _status, rank_expr = _filtered_query(db, user, **params)
|
||||
query, rank_expr = _filtered_query(db, user, **params)
|
||||
keys = _sort_keys(sort, seed, rank_expr)
|
||||
|
||||
# Expose each key column on the row so we can build the next cursor from the last item.
|
||||
|
|
@ -522,7 +522,7 @@ def get_feed_count(
|
|||
user: User = Depends(current_user),
|
||||
db: Session = Depends(get_db),
|
||||
) -> dict:
|
||||
query, _status, _rank = _filtered_query(db, user, **params)
|
||||
query, _rank = _filtered_query(db, user, **params)
|
||||
total = db.scalar(select(func.count()).select_from(query.subquery()))
|
||||
return {"count": total or 0}
|
||||
|
||||
|
|
@ -550,7 +550,7 @@ def get_facets(
|
|||
# keeps them applied, so each remaining chip narrows to channels that ALSO have all
|
||||
# already-selected topics — and tags that can't co-occur drop to zero (hidden).
|
||||
conjunctive = category == "topic" and params.get("tag_mode") == "and"
|
||||
base, _status, _rank = _filtered_query(
|
||||
base, _rank = _filtered_query(
|
||||
db,
|
||||
user,
|
||||
**{**params, "exclude_tag_category": None if conjunctive else category},
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ from app import quota, sysconfig
|
|||
from app.auth import require_human
|
||||
from app.db import get_db
|
||||
from app.models import (
|
||||
LIVE_OR_UPCOMING,
|
||||
BlockedChannel,
|
||||
Channel,
|
||||
Playlist,
|
||||
|
|
@ -45,8 +46,6 @@ router = APIRouter(prefix="/api/search", tags=["search"])
|
|||
|
||||
# search.list costs this many quota units per page.
|
||||
SEARCH_COST = 100
|
||||
# live_status values we never surface from a live search (was_live VODs are real content).
|
||||
_LIVE_HIDDEN = ("live", "upcoming")
|
||||
|
||||
|
||||
def _classify_shorts(db: Session, videos: list[Video]) -> None:
|
||||
|
|
@ -165,7 +164,7 @@ def _ingest_candidates(
|
|||
# 4) Confirm/deny Shorts (no quota) so none enter via search.
|
||||
_classify_shorts(db, videos)
|
||||
|
||||
keep = {v.id for v in videos if not v.is_short and v.live_status not in _LIVE_HIDDEN}
|
||||
keep = {v.id for v in videos if not v.is_short and v.live_status not in LIVE_OR_UPCOMING}
|
||||
return [vid for vid in ids if vid in keep]
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue