diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-02-13 22:01:12 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-02-13 22:01:12 +0900 |
| commit | e216c3bc97994b4172d15d52b46d5f6b75f35ea4 (patch) | |
| tree | 3ffbd74f4cb2d90846931c8dcbb97ec07f2b91f1 /backend/db/articles.sql.go | |
| parent | c863e64c0521926e785f4aa7ecf4cf15bb9defa7 (diff) | |
| download | feedaka-e216c3bc97994b4172d15d52b46d5f6b75f35ea4.tar.gz feedaka-e216c3bc97994b4172d15d52b46d5f6b75f35ea4.tar.zst feedaka-e216c3bc97994b4172d15d52b46d5f6b75f35ea4.zip | |
feat: add feed sidebar and cursor-based pagination
Add a feed sidebar to /unread and /read pages for filtering articles by
feed, and replace the fixed 100-article limit with cursor-based
pagination using a "Load more" button.
Backend:
- Add PageInfo, ArticleConnection types and pagination args to GraphQL
- Replace GetUnreadArticles/GetReadArticles with parameterized queries
- Add GetFeedUnreadCounts query and composite index
- Add shared pagination helper in resolver
Frontend:
- Add FeedSidebar component with unread count badges
- Add usePaginatedArticles hook for cursor-based fetching
- Update ArticleList with Load more button and single-feed mode
- Use ?feed=<id> query parameter for feed filtering
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'backend/db/articles.sql.go')
| -rw-r--r-- | backend/db/articles.sql.go | 192 |
1 files changed, 176 insertions, 16 deletions
diff --git a/backend/db/articles.sql.go b/backend/db/articles.sql.go index 7f6400b..329cb18 100644 --- a/backend/db/articles.sql.go +++ b/backend/db/articles.sql.go @@ -192,18 +192,96 @@ func (q *Queries) GetArticlesByFeed(ctx context.Context, feedID int64) ([]Articl return items, nil } -const getReadArticles = `-- name: GetReadArticles :many +const getArticlesByFeedPaginated = `-- name: GetArticlesByFeedPaginated :many SELECT a.id, a.feed_id, a.guid, a.title, a.url, a.is_read, f.id as feed_id_2, f.url as feed_url, f.title as feed_title, f.is_subscribed as feed_is_subscribed FROM articles AS a INNER JOIN feeds AS f ON a.feed_id = f.id -WHERE a.is_read = 1 AND f.is_subscribed = 1 AND f.user_id = ? +WHERE a.is_read = ? AND f.is_subscribed = 1 AND f.user_id = ? AND a.feed_id = ? ORDER BY a.id DESC -LIMIT 100 +LIMIT ? ` -type GetReadArticlesRow struct { +type GetArticlesByFeedPaginatedParams struct { + IsRead int64 + UserID int64 + FeedID int64 + Limit int64 +} + +type GetArticlesByFeedPaginatedRow struct { + ID int64 + FeedID int64 + Guid string + Title string + Url string + IsRead int64 + FeedID2 int64 + FeedUrl string + FeedTitle string + FeedIsSubscribed int64 +} + +func (q *Queries) GetArticlesByFeedPaginated(ctx context.Context, arg GetArticlesByFeedPaginatedParams) ([]GetArticlesByFeedPaginatedRow, error) { + rows, err := q.db.QueryContext(ctx, getArticlesByFeedPaginated, + arg.IsRead, + arg.UserID, + arg.FeedID, + arg.Limit, + ) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetArticlesByFeedPaginatedRow{} + for rows.Next() { + var i GetArticlesByFeedPaginatedRow + if err := rows.Scan( + &i.ID, + &i.FeedID, + &i.Guid, + &i.Title, + &i.Url, + &i.IsRead, + &i.FeedID2, + &i.FeedUrl, + &i.FeedTitle, + &i.FeedIsSubscribed, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getArticlesByFeedPaginatedAfter = `-- name: GetArticlesByFeedPaginatedAfter :many +SELECT + a.id, a.feed_id, a.guid, a.title, a.url, a.is_read, + f.id as feed_id_2, f.url as feed_url, f.title as feed_title, f.is_subscribed as feed_is_subscribed +FROM articles AS a +INNER JOIN feeds AS f ON a.feed_id = f.id +WHERE a.is_read = ? AND f.is_subscribed = 1 AND f.user_id = ? AND a.feed_id = ? AND a.id < ? +ORDER BY a.id DESC +LIMIT ? +` + +type GetArticlesByFeedPaginatedAfterParams struct { + IsRead int64 + UserID int64 + FeedID int64 + ID int64 + Limit int64 +} + +type GetArticlesByFeedPaginatedAfterRow struct { ID int64 FeedID int64 Guid string @@ -216,15 +294,85 @@ type GetReadArticlesRow struct { FeedIsSubscribed int64 } -func (q *Queries) GetReadArticles(ctx context.Context, userID int64) ([]GetReadArticlesRow, error) { - rows, err := q.db.QueryContext(ctx, getReadArticles, userID) +func (q *Queries) GetArticlesByFeedPaginatedAfter(ctx context.Context, arg GetArticlesByFeedPaginatedAfterParams) ([]GetArticlesByFeedPaginatedAfterRow, error) { + rows, err := q.db.QueryContext(ctx, getArticlesByFeedPaginatedAfter, + arg.IsRead, + arg.UserID, + arg.FeedID, + arg.ID, + arg.Limit, + ) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetArticlesByFeedPaginatedAfterRow{} + for rows.Next() { + var i GetArticlesByFeedPaginatedAfterRow + if err := rows.Scan( + &i.ID, + &i.FeedID, + &i.Guid, + &i.Title, + &i.Url, + &i.IsRead, + &i.FeedID2, + &i.FeedUrl, + &i.FeedTitle, + &i.FeedIsSubscribed, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getArticlesPaginated = `-- name: GetArticlesPaginated :many +SELECT + a.id, a.feed_id, a.guid, a.title, a.url, a.is_read, + f.id as feed_id_2, f.url as feed_url, f.title as feed_title, f.is_subscribed as feed_is_subscribed +FROM articles AS a +INNER JOIN feeds AS f ON a.feed_id = f.id +WHERE a.is_read = ? AND f.is_subscribed = 1 AND f.user_id = ? +ORDER BY a.id DESC +LIMIT ? +` + +type GetArticlesPaginatedParams struct { + IsRead int64 + UserID int64 + Limit int64 +} + +type GetArticlesPaginatedRow struct { + ID int64 + FeedID int64 + Guid string + Title string + Url string + IsRead int64 + FeedID2 int64 + FeedUrl string + FeedTitle string + FeedIsSubscribed int64 +} + +func (q *Queries) GetArticlesPaginated(ctx context.Context, arg GetArticlesPaginatedParams) ([]GetArticlesPaginatedRow, error) { + rows, err := q.db.QueryContext(ctx, getArticlesPaginated, arg.IsRead, arg.UserID, arg.Limit) if err != nil { return nil, err } defer rows.Close() - items := []GetReadArticlesRow{} + items := []GetArticlesPaginatedRow{} for rows.Next() { - var i GetReadArticlesRow + var i GetArticlesPaginatedRow if err := rows.Scan( &i.ID, &i.FeedID, @@ -250,18 +398,25 @@ func (q *Queries) GetReadArticles(ctx context.Context, userID int64) ([]GetReadA return items, nil } -const getUnreadArticles = `-- name: GetUnreadArticles :many +const getArticlesPaginatedAfter = `-- name: GetArticlesPaginatedAfter :many SELECT a.id, a.feed_id, a.guid, a.title, a.url, a.is_read, f.id as feed_id_2, f.url as feed_url, f.title as feed_title, f.is_subscribed as feed_is_subscribed FROM articles AS a INNER JOIN feeds AS f ON a.feed_id = f.id -WHERE a.is_read = 0 AND f.is_subscribed = 1 AND f.user_id = ? +WHERE a.is_read = ? AND f.is_subscribed = 1 AND f.user_id = ? AND a.id < ? ORDER BY a.id DESC -LIMIT 100 +LIMIT ? ` -type GetUnreadArticlesRow struct { +type GetArticlesPaginatedAfterParams struct { + IsRead int64 + UserID int64 + ID int64 + Limit int64 +} + +type GetArticlesPaginatedAfterRow struct { ID int64 FeedID int64 Guid string @@ -274,15 +429,20 @@ type GetUnreadArticlesRow struct { FeedIsSubscribed int64 } -func (q *Queries) GetUnreadArticles(ctx context.Context, userID int64) ([]GetUnreadArticlesRow, error) { - rows, err := q.db.QueryContext(ctx, getUnreadArticles, userID) +func (q *Queries) GetArticlesPaginatedAfter(ctx context.Context, arg GetArticlesPaginatedAfterParams) ([]GetArticlesPaginatedAfterRow, error) { + rows, err := q.db.QueryContext(ctx, getArticlesPaginatedAfter, + arg.IsRead, + arg.UserID, + arg.ID, + arg.Limit, + ) if err != nil { return nil, err } defer rows.Close() - items := []GetUnreadArticlesRow{} + items := []GetArticlesPaginatedAfterRow{} for rows.Next() { - var i GetUnreadArticlesRow + var i GetArticlesPaginatedAfterRow if err := rows.Scan( &i.ID, &i.FeedID, |
