Filter read articles in recommendations and mark read statusUnverified
144a7d4 parent: e38807c modified
internal/cluster/scoring.go +6 -2 | @@ -36,6 +36,7 @@ type ArticleRecommendation struct { | ||
| 36 | 36 | Author string |
| 37 | 37 | Summary string |
| 38 | 38 | Published sql.NullTime |
| 39 | + IsRead bool | |
| 39 | 40 | Score float64 |
| 40 | 41 | } |
| 41 | 42 | |
| @@ -218,6 +219,7 @@ func (e *Engine) ComputeArticleRecommendationsOnDemand(ctx context.Context, user | ||
| 218 | 219 | SELECT a.id, a.title, COALESCE(a.url, ''), la.feed_url, COALESCE(f.title, ''), |
| 219 | 220 | COALESCE(f.favicon_url, ''), |
| 220 | 221 | COALESCE(a.author, ''), COALESCE(a.summary, ''), a.published, |
| 222 | + COALESCE(rs.is_read, 0), | |
| 221 | 223 | COALESCE(la.like_signal, 0) * ? |
| 222 | 224 | + COALESCE(sl.social, 0) * ? |
| 223 | 225 | + EXP(-0.023 * CAST(julianday('now') - julianday(a.published) AS REAL)) * 0.2 |
| @@ -226,9 +228,11 @@ func (e *Engine) ComputeArticleRecommendationsOnDemand(ctx context.Context, user | ||
| 226 | 228 | JOIN articles.articles a ON a.feed_url = la.feed_url AND a.url = la.article_url |
| 227 | 229 | LEFT JOIN articles.feeds f ON f.feed_url = la.feed_url |
| 228 | 230 | LEFT JOIN social_likes sl ON sl.feed_url = la.feed_url AND sl.article_url = la.article_url |
| 231 | + LEFT JOIN articles.read_state rs ON rs.article_id = a.id AND rs.user_did = ? | |
| 232 | + WHERE COALESCE(rs.is_read, 0) = 0 | |
| 229 | 233 | ORDER BY score DESC, (CASE WHEN a.published > 'now' THEN 1 ELSE 0 END), a.published DESC |
| 230 | 234 | LIMIT ? |
| 231 | - `, userDID, userDID, userDID, userDID, userDID, userDID, w.WLike, w.WSocial, limit) | |
| 235 | + `, userDID, userDID, userDID, userDID, userDID, userDID, w.WLike, w.WSocial, userDID, limit) | |
| 232 | 236 | if err != nil { |
| 233 | 237 | return nil, err |
| 234 | 238 | } |
| @@ -238,7 +242,7 @@ func (e *Engine) ComputeArticleRecommendationsOnDemand(ctx context.Context, user | ||
| 238 | 242 | for rows.Next() { |
| 239 | 243 | rec := &ArticleRecommendation{} |
| 240 | 244 | if err := rows.Scan(&rec.ArticleID, &rec.Title, &rec.URL, &rec.FeedURL, &rec.FeedTitle, |
| 241 | - &rec.FaviconURL, &rec.Author, &rec.Summary, &rec.Published, &rec.Score); err != nil { | |
| 245 | + &rec.FaviconURL, &rec.Author, &rec.Summary, &rec.Published, &rec.IsRead, &rec.Score); err != nil { | |
| 242 | 246 | return nil, err |
| 243 | 247 | } |
| 244 | 248 | recs = append(recs, rec) |
| @@ -36,6 +36,7 @@ type ArticleRecommendation struct { | |||
| 36 | Author string | 36 | Author string |
| 37 | Summary string | 37 | Summary string |
| 38 | Published sql.NullTime | 38 | Published sql.NullTime |
| 39 | + IsRead bool | ||
| 39 | Score float64 | 40 | Score float64 |
| 40 | } | 41 | } |
| 41 | 42 | ||
| @@ -218,6 +219,7 @@ func (e *Engine) ComputeArticleRecommendationsOnDemand(ctx context.Context, user | |||
| 218 | SELECT a.id, a.title, COALESCE(a.url, ''), la.feed_url, COALESCE(f.title, ''), | 219 | SELECT a.id, a.title, COALESCE(a.url, ''), la.feed_url, COALESCE(f.title, ''), |
| 219 | COALESCE(f.favicon_url, ''), | 220 | COALESCE(f.favicon_url, ''), |
| 220 | COALESCE(a.author, ''), COALESCE(a.summary, ''), a.published, | 221 | COALESCE(a.author, ''), COALESCE(a.summary, ''), a.published, |
| 222 | + COALESCE(rs.is_read, 0), | ||
| 221 | COALESCE(la.like_signal, 0) * ? | 223 | COALESCE(la.like_signal, 0) * ? |
| 222 | + COALESCE(sl.social, 0) * ? | 224 | + COALESCE(sl.social, 0) * ? |
| 223 | + EXP(-0.023 * CAST(julianday('now') - julianday(a.published) AS REAL)) * 0.2 | 225 | + EXP(-0.023 * CAST(julianday('now') - julianday(a.published) AS REAL)) * 0.2 |
| @@ -226,9 +228,11 @@ func (e *Engine) ComputeArticleRecommendationsOnDemand(ctx context.Context, user | |||
| 226 | JOIN articles.articles a ON a.feed_url = la.feed_url AND a.url = la.article_url | 228 | JOIN articles.articles a ON a.feed_url = la.feed_url AND a.url = la.article_url |
| 227 | LEFT JOIN articles.feeds f ON f.feed_url = la.feed_url | 229 | LEFT JOIN articles.feeds f ON f.feed_url = la.feed_url |
| 228 | LEFT JOIN social_likes sl ON sl.feed_url = la.feed_url AND sl.article_url = la.article_url | 230 | LEFT JOIN social_likes sl ON sl.feed_url = la.feed_url AND sl.article_url = la.article_url |
| 231 | + LEFT JOIN articles.read_state rs ON rs.article_id = a.id AND rs.user_did = ? | ||
| 232 | + WHERE COALESCE(rs.is_read, 0) = 0 | ||
| 229 | ORDER BY score DESC, (CASE WHEN a.published > 'now' THEN 1 ELSE 0 END), a.published DESC | 233 | ORDER BY score DESC, (CASE WHEN a.published > 'now' THEN 1 ELSE 0 END), a.published DESC |
| 230 | LIMIT ? | 234 | LIMIT ? |
| 231 | - `, userDID, userDID, userDID, userDID, userDID, userDID, w.WLike, w.WSocial, limit) | 235 | + `, userDID, userDID, userDID, userDID, userDID, userDID, w.WLike, w.WSocial, userDID, limit) |
| 232 | if err != nil { | 236 | if err != nil { |
| 233 | return nil, err | 237 | return nil, err |
| 234 | } | 238 | } |
| @@ -238,7 +242,7 @@ func (e *Engine) ComputeArticleRecommendationsOnDemand(ctx context.Context, user | |||
| 238 | for rows.Next() { | 242 | for rows.Next() { |
| 239 | rec := &ArticleRecommendation{} | 243 | rec := &ArticleRecommendation{} |
| 240 | if err := rows.Scan(&rec.ArticleID, &rec.Title, &rec.URL, &rec.FeedURL, &rec.FeedTitle, | 244 | if err := rows.Scan(&rec.ArticleID, &rec.Title, &rec.URL, &rec.FeedURL, &rec.FeedTitle, |
| 241 | - &rec.FaviconURL, &rec.Author, &rec.Summary, &rec.Published, &rec.Score); err != nil { | 245 | + &rec.FaviconURL, &rec.Author, &rec.Summary, &rec.Published, &rec.IsRead, &rec.Score); err != nil { |
| 242 | return nil, err | 246 | return nil, err |
| 243 | } | 247 | } |
| 244 | recs = append(recs, rec) | 248 | recs = append(recs, rec) |
modified
internal/tmpl/partials/recommendation-article-card.html +1 -1 | @@ -3,7 +3,7 @@ | ||
| 3 | 3 | <div class="flex items-start justify-between gap-4"> |
| 4 | 4 | <div class="min-w-0 flex-1"> |
| 5 | 5 | <div class="flex items-center gap-2"> |
| 6 | - <span class="w-2 h-2 rounded-full bg-spot-green shrink-0"></span> | |
| 6 | + {{if not .IsRead}}<span class="w-2 h-2 rounded-full bg-spot-green shrink-0"></span>{{end}} | |
| 7 | 7 | <a href="/articles/{{.ArticleID}}" class="font-bold text-spot-text hover:text-spot-green transition text-lg leading-tight">{{.Title}}</a> |
| 8 | 8 | </div> |
| 9 | 9 | <div class="text-sm text-spot-secondary mt-1 flex items-center gap-2"> |
| @@ -3,7 +3,7 @@ | |||
| 3 | <div class="flex items-start justify-between gap-4"> | 3 | <div class="flex items-start justify-between gap-4"> |
| 4 | <div class="min-w-0 flex-1"> | 4 | <div class="min-w-0 flex-1"> |
| 5 | <div class="flex items-center gap-2"> | 5 | <div class="flex items-center gap-2"> |
| 6 | - <span class="w-2 h-2 rounded-full bg-spot-green shrink-0"></span> | 6 | + {{if not .IsRead}}<span class="w-2 h-2 rounded-full bg-spot-green shrink-0"></span>{{end}} |
| 7 | <a href="/articles/{{.ArticleID}}" class="font-bold text-spot-text hover:text-spot-green transition text-lg leading-tight">{{.Title}}</a> | 7 | <a href="/articles/{{.ArticleID}}" class="font-bold text-spot-text hover:text-spot-green transition text-lg leading-tight">{{.Title}}</a> |
| 8 | </div> | 8 | </div> |
| 9 | <div class="text-sm text-spot-secondary mt-1 flex items-center gap-2"> | 9 | <div class="text-sm text-spot-secondary mt-1 flex items-center gap-2"> |