Fix pagination on unread and read pageUnverified
a934412 parent: 9755234 modified
internal/db/article.go +13 -1 | @@ -421,7 +421,7 @@ func (s *ArticleStore) CountNewArticles(ctx context.Context, userDID string, sin | ||
| 421 | 421 | return count, err |
| 422 | 422 | } |
| 423 | 423 | |
| 424 | -func (s *ArticleStore) GetNextArticleID(ctx context.Context, userDID string, articleID int64, feedURL string, liked bool) (*int64, error) { | |
| 424 | +func (s *ArticleStore) GetNextArticleID(ctx context.Context, userDID string, articleID int64, feedURL string, liked bool, status string) (*int64, error) { | |
| 425 | 425 | var fromParts []string |
| 426 | 426 | var whereParts []string |
| 427 | 427 | |
| @@ -437,6 +437,15 @@ func (s *ArticleStore) GetNextArticleID(ctx context.Context, userDID string, art | ||
| 437 | 437 | fromParts = append(fromParts, "JOIN articles.likes l ON l.author_did = ? AND l.article_url = a.url") |
| 438 | 438 | } |
| 439 | 439 | |
| 440 | + switch status { | |
| 441 | + case "unread": | |
| 442 | + fromParts = append(fromParts, "LEFT JOIN articles.read_state rs ON rs.user_did = ? AND rs.article_id = a.id") | |
| 443 | + whereParts = append(whereParts, "(rs.is_read = 0 OR rs.is_read IS NULL)") | |
| 444 | + case "read": | |
| 445 | + fromParts = append(fromParts, "JOIN articles.read_state rs ON rs.user_did = ? AND rs.article_id = a.id") | |
| 446 | + whereParts = append(whereParts, "rs.is_read = 1") | |
| 447 | + } | |
| 448 | + | |
| 440 | 449 | whereParts = append(whereParts, "a.id != ?") |
| 441 | 450 | |
| 442 | 451 | fromClause := strings.Join(fromParts, " ") |
| @@ -464,6 +473,9 @@ func (s *ArticleStore) GetNextArticleID(ctx context.Context, userDID string, art | ||
| 464 | 473 | if liked { |
| 465 | 474 | args = append(args, userDID) |
| 466 | 475 | } |
| 476 | + if status == "unread" || status == "read" { | |
| 477 | + args = append(args, userDID) | |
| 478 | + } | |
| 467 | 479 | args = append(args, articleID, articleID) |
| 468 | 480 | |
| 469 | 481 | var next sql.NullInt64 |
| @@ -421,7 +421,7 @@ func (s *ArticleStore) CountNewArticles(ctx context.Context, userDID string, sin | |||
| 421 | return count, err | 421 | return count, err |
| 422 | } | 422 | } |
| 423 | 423 | ||
| 424 | -func (s *ArticleStore) GetNextArticleID(ctx context.Context, userDID string, articleID int64, feedURL string, liked bool) (*int64, error) { | 424 | +func (s *ArticleStore) GetNextArticleID(ctx context.Context, userDID string, articleID int64, feedURL string, liked bool, status string) (*int64, error) { |
| 425 | var fromParts []string | 425 | var fromParts []string |
| 426 | var whereParts []string | 426 | var whereParts []string |
| 427 | 427 | ||
| @@ -437,6 +437,15 @@ func (s *ArticleStore) GetNextArticleID(ctx context.Context, userDID string, art | |||
| 437 | fromParts = append(fromParts, "JOIN articles.likes l ON l.author_did = ? AND l.article_url = a.url") | 437 | fromParts = append(fromParts, "JOIN articles.likes l ON l.author_did = ? AND l.article_url = a.url") |
| 438 | } | 438 | } |
| 439 | 439 | ||
| 440 | + switch status { | ||
| 441 | + case "unread": | ||
| 442 | + fromParts = append(fromParts, "LEFT JOIN articles.read_state rs ON rs.user_did = ? AND rs.article_id = a.id") | ||
| 443 | + whereParts = append(whereParts, "(rs.is_read = 0 OR rs.is_read IS NULL)") | ||
| 444 | + case "read": | ||
| 445 | + fromParts = append(fromParts, "JOIN articles.read_state rs ON rs.user_did = ? AND rs.article_id = a.id") | ||
| 446 | + whereParts = append(whereParts, "rs.is_read = 1") | ||
| 447 | + } | ||
| 448 | + | ||
| 440 | whereParts = append(whereParts, "a.id != ?") | 449 | whereParts = append(whereParts, "a.id != ?") |
| 441 | 450 | ||
| 442 | fromClause := strings.Join(fromParts, " ") | 451 | fromClause := strings.Join(fromParts, " ") |
| @@ -464,6 +473,9 @@ func (s *ArticleStore) GetNextArticleID(ctx context.Context, userDID string, art | |||
| 464 | if liked { | 473 | if liked { |
| 465 | args = append(args, userDID) | 474 | args = append(args, userDID) |
| 466 | } | 475 | } |
| 476 | + if status == "unread" || status == "read" { | ||
| 477 | + args = append(args, userDID) | ||
| 478 | + } | ||
| 467 | args = append(args, articleID, articleID) | 479 | args = append(args, articleID, articleID) |
| 468 | 480 | ||
| 469 | var next sql.NullInt64 | 481 | var next sql.NullInt64 |
modified
internal/server/annotations_handler.go +1 -1 | @@ -57,7 +57,7 @@ func (s *Server) handleLibrary(w http.ResponseWriter, r *http.Request) { | ||
| 57 | 57 | likedPage.HasNext = true |
| 58 | 58 | likedPage.NextPage = likedPage.Page + 1 |
| 59 | 59 | } |
| 60 | - navSuffix := buildNavSuffix("", true) | |
| 60 | + navSuffix := buildNavSuffix("", true, "") | |
| 61 | 61 | for _, a := range articles { |
| 62 | 62 | a.NavSuffix = navSuffix |
| 63 | 63 | } |
| @@ -57,7 +57,7 @@ func (s *Server) handleLibrary(w http.ResponseWriter, r *http.Request) { | |||
| 57 | likedPage.HasNext = true | 57 | likedPage.HasNext = true |
| 58 | likedPage.NextPage = likedPage.Page + 1 | 58 | likedPage.NextPage = likedPage.Page + 1 |
| 59 | } | 59 | } |
| 60 | - navSuffix := buildNavSuffix("", true) | 60 | + navSuffix := buildNavSuffix("", true, "") |
| 61 | for _, a := range articles { | 61 | for _, a := range articles { |
| 62 | a.NavSuffix = navSuffix | 62 | a.NavSuffix = navSuffix |
| 63 | } | 63 | } |
modified
internal/server/articles_handler.go +8 -4 | @@ -89,7 +89,7 @@ func (s *Server) handleArticles(w http.ResponseWriter, r *http.Request) { | ||
| 89 | 89 | articles = articles[:page.PageSize] |
| 90 | 90 | } |
| 91 | 91 | |
| 92 | - navSuffix := buildNavSuffix(feedURL, false) | |
| 92 | + navSuffix := buildNavSuffix(feedURL, false, status) | |
| 93 | 93 | for _, a := range articles { |
| 94 | 94 | a.NavSuffix = navSuffix |
| 95 | 95 | } |
| @@ -192,6 +192,7 @@ func (s *Server) handleArticleDetail(w http.ResponseWriter, r *http.Request) { | ||
| 192 | 192 | |
| 193 | 193 | fromFeedURL := r.URL.Query().Get("from_feed") |
| 194 | 194 | navLiked := r.URL.Query().Get("liked") == "1" |
| 195 | + navStatus := r.URL.Query().Get("status") | |
| 195 | 196 | |
| 196 | 197 | g, gCtx := errgroup.WithContext(ctx) |
| 197 | 198 | |
| @@ -255,7 +256,7 @@ func (s *Server) handleArticleDetail(w http.ResponseWriter, r *http.Request) { | ||
| 255 | 256 | |
| 256 | 257 | g.Go(func() error { |
| 257 | 258 | var err error |
| 258 | - nextID, err = s.dbs.Articles.GetNextArticleID(gCtx, user.DID, id, fromFeedURL, navLiked) | |
| 259 | + nextID, err = s.dbs.Articles.GetNextArticleID(gCtx, user.DID, id, fromFeedURL, navLiked, navStatus) | |
| 259 | 260 | if err != nil { |
| 260 | 261 | s.logger.Warn("failed to get next article", "error", err, "id", id) |
| 261 | 262 | } |
| @@ -276,7 +277,7 @@ func (s *Server) handleArticleDetail(w http.ResponseWriter, r *http.Request) { | ||
| 276 | 277 | "HasLiked": liked, |
| 277 | 278 | "Annotations": annotations, |
| 278 | 279 | "NextID": nextID, |
| 279 | - "NextSuffix": buildNavSuffix(fromFeedURL, navLiked), | |
| 280 | + "NextSuffix": buildNavSuffix(fromFeedURL, navLiked, navStatus), | |
| 280 | 281 | }) |
| 281 | 282 | } |
| 282 | 283 | |
| @@ -478,7 +479,7 @@ func (s *Server) handleFetchContent(w http.ResponseWriter, r *http.Request) { | ||
| 478 | 479 | s.logger.Info("scraped article content", "id", id, "url", article.URL.String, "content_len", len(cleaned)) |
| 479 | 480 | } |
| 480 | 481 | |
| 481 | -func buildNavSuffix(feedURL string, liked bool) string { | |
| 482 | +func buildNavSuffix(feedURL string, liked bool, status string) string { | |
| 482 | 483 | v := url.Values{} |
| 483 | 484 | if feedURL != "" { |
| 484 | 485 | v.Set("from_feed", feedURL) |
| @@ -486,6 +487,9 @@ func buildNavSuffix(feedURL string, liked bool) string { | ||
| 486 | 487 | if liked { |
| 487 | 488 | v.Set("liked", "1") |
| 488 | 489 | } |
| 490 | + if status != "" { | |
| 491 | + v.Set("status", status) | |
| 492 | + } | |
| 489 | 493 | if len(v) == 0 { |
| 490 | 494 | return "" |
| 491 | 495 | } |
| @@ -89,7 +89,7 @@ func (s *Server) handleArticles(w http.ResponseWriter, r *http.Request) { | |||
| 89 | articles = articles[:page.PageSize] | 89 | articles = articles[:page.PageSize] |
| 90 | } | 90 | } |
| 91 | 91 | ||
| 92 | - navSuffix := buildNavSuffix(feedURL, false) | 92 | + navSuffix := buildNavSuffix(feedURL, false, status) |
| 93 | for _, a := range articles { | 93 | for _, a := range articles { |
| 94 | a.NavSuffix = navSuffix | 94 | a.NavSuffix = navSuffix |
| 95 | } | 95 | } |
| @@ -192,6 +192,7 @@ func (s *Server) handleArticleDetail(w http.ResponseWriter, r *http.Request) { | |||
| 192 | 192 | ||
| 193 | fromFeedURL := r.URL.Query().Get("from_feed") | 193 | fromFeedURL := r.URL.Query().Get("from_feed") |
| 194 | navLiked := r.URL.Query().Get("liked") == "1" | 194 | navLiked := r.URL.Query().Get("liked") == "1" |
| 195 | + navStatus := r.URL.Query().Get("status") | ||
| 195 | 196 | ||
| 196 | g, gCtx := errgroup.WithContext(ctx) | 197 | g, gCtx := errgroup.WithContext(ctx) |
| 197 | 198 | ||
| @@ -255,7 +256,7 @@ func (s *Server) handleArticleDetail(w http.ResponseWriter, r *http.Request) { | |||
| 255 | 256 | ||
| 256 | g.Go(func() error { | 257 | g.Go(func() error { |
| 257 | var err error | 258 | var err error |
| 258 | - nextID, err = s.dbs.Articles.GetNextArticleID(gCtx, user.DID, id, fromFeedURL, navLiked) | 259 | + nextID, err = s.dbs.Articles.GetNextArticleID(gCtx, user.DID, id, fromFeedURL, navLiked, navStatus) |
| 259 | if err != nil { | 260 | if err != nil { |
| 260 | s.logger.Warn("failed to get next article", "error", err, "id", id) | 261 | s.logger.Warn("failed to get next article", "error", err, "id", id) |
| 261 | } | 262 | } |
| @@ -276,7 +277,7 @@ func (s *Server) handleArticleDetail(w http.ResponseWriter, r *http.Request) { | |||
| 276 | "HasLiked": liked, | 277 | "HasLiked": liked, |
| 277 | "Annotations": annotations, | 278 | "Annotations": annotations, |
| 278 | "NextID": nextID, | 279 | "NextID": nextID, |
| 279 | - "NextSuffix": buildNavSuffix(fromFeedURL, navLiked), | 280 | + "NextSuffix": buildNavSuffix(fromFeedURL, navLiked, navStatus), |
| 280 | }) | 281 | }) |
| 281 | } | 282 | } |
| 282 | 283 | ||
| @@ -478,7 +479,7 @@ func (s *Server) handleFetchContent(w http.ResponseWriter, r *http.Request) { | |||
| 478 | s.logger.Info("scraped article content", "id", id, "url", article.URL.String, "content_len", len(cleaned)) | 479 | s.logger.Info("scraped article content", "id", id, "url", article.URL.String, "content_len", len(cleaned)) |
| 479 | } | 480 | } |
| 480 | 481 | ||
| 481 | -func buildNavSuffix(feedURL string, liked bool) string { | 482 | +func buildNavSuffix(feedURL string, liked bool, status string) string { |
| 482 | v := url.Values{} | 483 | v := url.Values{} |
| 483 | if feedURL != "" { | 484 | if feedURL != "" { |
| 484 | v.Set("from_feed", feedURL) | 485 | v.Set("from_feed", feedURL) |
| @@ -486,6 +487,9 @@ func buildNavSuffix(feedURL string, liked bool) string { | |||
| 486 | if liked { | 487 | if liked { |
| 487 | v.Set("liked", "1") | 488 | v.Set("liked", "1") |
| 488 | } | 489 | } |
| 490 | + if status != "" { | ||
| 491 | + v.Set("status", status) | ||
| 492 | + } | ||
| 489 | if len(v) == 0 { | 493 | if len(v) == 0 { |
| 490 | return "" | 494 | return "" |
| 491 | } | 495 | } |