feat(feed): per-user search finds in Mine scope + full-text relevance search
Two related search improvements: 1) Your own live-search results now belong to your Mine feed. A new per-user search_finds table (migration 0030) records each video you surface via your YouTube search (the route inserts them idempotently). The Mine feed becomes 'your non-hidden subscriptions OR your search finds', and the Source filter now applies in Mine too: organic = subscriptions, search = your search finds, all = both (default stays organic, so the main feed is unchanged). The shared Library keeps using the global via_search flag. 2) Feed search ranks by relevance instead of a whole-phrase substring. A custom unaccent_simple text-search config + GIN index (migration 0031) back a YouTube-like fuzzy match: word-order-independent, multi-word AND, prefix on the word being typed, accent-insensitive. A new 'relevance' sort orders by ts_rank; the channel name still matches as a substring. The rank is scaled to an integer so the keyset cursor pages it exactly (a raw float4 breaks paging). _filtered_query returns the rank expr so only the feed list uses it.
This commit is contained in:
parent
a4abfa402f
commit
6cef826ecc
5 changed files with 208 additions and 22 deletions
|
|
@ -17,12 +17,13 @@ from datetime import datetime, timezone
|
|||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy import and_, select
|
||||
from sqlalchemy.dialects.postgresql import insert as pg_insert
|
||||
from sqlalchemy.orm import Session, aliased
|
||||
|
||||
from app import quota, sysconfig
|
||||
from app.auth import require_human
|
||||
from app.db import get_db
|
||||
from app.models import Channel, User, Video, VideoState
|
||||
from app.models import Channel, SearchFind, User, Video, VideoState
|
||||
from app.routes.feed import _serialize, feed_columns
|
||||
from app.sync.subscriptions import apply_channel_details
|
||||
from app.sync.videos import _insert_stubs, apply_video_details, parse_dt
|
||||
|
|
@ -213,6 +214,17 @@ def search_youtube(
|
|||
v.id for v in videos if not v.is_short and v.live_status not in _LIVE_HIDDEN
|
||||
}
|
||||
ordered = [vid for vid in ids if vid in keep]
|
||||
|
||||
# 6) Record these as THIS user's search finds so they surface in their own Mine feed
|
||||
# (and the Mine Source filter), independent of the global via_search flag. Idempotent.
|
||||
if ordered:
|
||||
db.execute(
|
||||
pg_insert(SearchFind)
|
||||
.values([{"user_id": user.id, "video_id": vid} for vid in ordered])
|
||||
.on_conflict_do_nothing(index_elements=["user_id", "video_id"])
|
||||
)
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"items": _serialize_ids(db, user, ordered),
|
||||
"next_cursor": page["next_page_token"],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue