Follow source/topic — account-backed personalization (v1)
Per Codex — turn accounts into a real reason to return, without an algorithmic feed. Durable interests (sources + tags), not moods. * DB: user_follows (user_id, kind source|tag, value, unique). * queries.feed gains follow_sources/follow_tags → the Following feed is "articles from a followed source OR carrying a followed tag", still respecting calm filters/boundaries. * API: GET/POST/DELETE /api/follows (sign-in required; source ids validated); /api/feed?following=true resolves the user's follows (anon → empty, not error). * Frontend: follows store (followKeys + toggleFollow, mirrors savedIds); a Follow button on source + tag/topic views; a "Following" lane in the nav with a tailored empty state; a Following management section in Account (unfollow). Digest "From what you follow" deferred to v2 (brief stays first). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,8 @@ def feed(
|
||||
tag: str | None = None,
|
||||
source_id: int | None = None,
|
||||
sort: str = "ranked",
|
||||
follow_sources: list[int] | None = None,
|
||||
follow_tags: list[str] | None = None,
|
||||
) -> list[dict]:
|
||||
"""Return articles with categorical filters applied in SQL.
|
||||
|
||||
@@ -114,6 +116,23 @@ def feed(
|
||||
clauses.append("a.source_id = ?")
|
||||
params.append(source_id)
|
||||
|
||||
# "Following" feed: articles from a followed source OR carrying a followed tag.
|
||||
# Passing either list (even empty) switches to following mode; no follows → none.
|
||||
if follow_sources is not None or follow_tags is not None:
|
||||
ors: list[str] = []
|
||||
for sid in follow_sources or []:
|
||||
params.append(sid)
|
||||
if follow_sources:
|
||||
ors.append(f"a.source_id IN ({','.join('?' * len(follow_sources))})")
|
||||
ftags = [t.lower() for t in (follow_tags or [])]
|
||||
if ftags:
|
||||
ors.append(
|
||||
f"EXISTS (SELECT 1 FROM article_tags at WHERE at.article_id = a.id "
|
||||
f"AND at.tag IN ({','.join('?' * len(ftags))}))"
|
||||
)
|
||||
params.extend(ftags)
|
||||
clauses.append("(" + " OR ".join(ors) + ")" if ors else "0")
|
||||
|
||||
where = "WHERE " + " AND ".join(clauses)
|
||||
params.extend([limit, offset])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user