From fd1cf079dce4bdcbc4b3eed68f725c85b91745af Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Tue, 30 Sep 2025 15:11:31 +0100 Subject: [PATCH] appview/notifications: fix pagination Change-Id: spvnpqlqqpkwttxslonnlowltvtzmllu Signed-off-by: oppiliappan --- appview/db/notifications.go | 31 +++++---- appview/notifications/notifications.go | 65 +++++++++---------- appview/pages/pages.go | 7 +- .../pages/templates/notifications/list.html | 63 +++++++++++++----- appview/pagination/page.go | 2 +- 5 files changed, 99 insertions(+), 69 deletions(-) diff --git a/appview/db/notifications.go b/appview/db/notifications.go index 68e3b825..6282ef56 100644 --- a/appview/db/notifications.go +++ b/appview/db/notifications.go @@ -3,7 +3,9 @@ package db import ( "context" "database/sql" + "errors" "fmt" + "strings" "time" "tangled.org/core/appview/models" @@ -248,22 +250,25 @@ func GetNotifications(e Execer, filters ...filter) ([]*models.Notification, erro return GetNotificationsPaginated(e, pagination.FirstPage(), filters...) } -func (d *DB) GetUnreadNotificationCount(ctx context.Context, userDID string) (int, error) { - recipientFilter := FilterEq("recipient_did", userDID) - readFilter := FilterEq("read", 0) +func CountNotifications(e Execer, filters ...filter) (int64, error) { + var conditions []string + var args []any + for _, filter := range filters { + conditions = append(conditions, filter.Condition()) + args = append(args, filter.Arg()...) + } - query := fmt.Sprintf(` - SELECT COUNT(*) - FROM notifications - WHERE %s AND %s - `, recipientFilter.Condition(), readFilter.Condition()) + whereClause := "" + if conditions != nil { + whereClause = " where " + strings.Join(conditions, " and ") + } - args := append(recipientFilter.Arg(), readFilter.Arg()...) + query := fmt.Sprintf(`select count(1) from notifications %s`, whereClause) + var count int64 + err := e.QueryRow(query, args...).Scan(&count) - var count int - err := d.DB.QueryRowContext(ctx, query, args...).Scan(&count) - if err != nil { - return 0, fmt.Errorf("failed to get unread count: %w", err) + if !errors.Is(err, sql.ErrNoRows) && err != nil { + return 0, err } return count, nil diff --git a/appview/notifications/notifications.go b/appview/notifications/notifications.go index de7c5b22..bb017df4 100644 --- a/appview/notifications/notifications.go +++ b/appview/notifications/notifications.go @@ -1,6 +1,7 @@ package notifications import ( + "fmt" "log" "net/http" "strconv" @@ -32,7 +33,7 @@ func (n *Notifications) Router(mw *middleware.Middleware) http.Handler { r.Use(middleware.AuthMiddleware(n.oauth)) - r.Get("/", n.notificationsPage) + r.With(middleware.Paginate).Get("/", n.notificationsPage) r.Get("/count", n.getUnreadCount) r.Post("/{id}/read", n.markRead) @@ -45,36 +46,33 @@ func (n *Notifications) Router(mw *middleware.Middleware) http.Handler { func (n *Notifications) notificationsPage(w http.ResponseWriter, r *http.Request) { userDid := n.oauth.GetDid(r) - limitStr := r.URL.Query().Get("limit") - offsetStr := r.URL.Query().Get("offset") - - limit := 20 // default - if limitStr != "" { - if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 100 { - limit = l - } + page, ok := r.Context().Value("page").(pagination.Page) + if !ok { + log.Println("failed to get page") + page = pagination.FirstPage() } - offset := 0 // default - if offsetStr != "" { - if o, err := strconv.Atoi(offsetStr); err == nil && o >= 0 { - offset = o - } + total, err := db.CountNotifications( + n.db, + db.FilterEq("recipient_did", userDid), + ) + if err != nil { + log.Println("failed to get total notifications:", err) + n.pages.Error500(w) + return } - page := pagination.Page{Limit: limit + 1, Offset: offset} - notifications, err := db.GetNotificationsWithEntities(n.db, page, db.FilterEq("recipient_did", userDid)) + notifications, err := db.GetNotificationsWithEntities( + n.db, + page, + db.FilterEq("recipient_did", userDid), + ) if err != nil { log.Println("failed to get notifications:", err) n.pages.Error500(w) return } - hasMore := len(notifications) > limit - if hasMore { - notifications = notifications[:limit] - } - err = n.db.MarkAllNotificationsRead(r.Context(), userDid) if err != nil { log.Println("failed to mark notifications as read:", err) @@ -88,27 +86,22 @@ func (n *Notifications) notificationsPage(w http.ResponseWriter, r *http.Request return } - params := pages.NotificationsParams{ + fmt.Println(n.pages.Notifications(w, pages.NotificationsParams{ LoggedInUser: user, Notifications: notifications, UnreadCount: unreadCount, - HasMore: hasMore, - NextOffset: offset + limit, - Limit: limit, - } - - err = n.pages.Notifications(w, params) - if err != nil { - log.Println("failed to load notifs:", err) - n.pages.Error500(w) - return - } + Page: page, + Total: total, + })) } func (n *Notifications) getUnreadCount(w http.ResponseWriter, r *http.Request) { - userDid := n.oauth.GetDid(r) - - count, err := n.db.GetUnreadNotificationCount(r.Context(), userDid) + user := n.oauth.GetUser(r) + count, err := db.CountNotifications( + n.db, + db.FilterEq("recipient_did", user.Did), + db.FilterEq("read", 0), + ) if err != nil { http.Error(w, "Failed to get unread count", http.StatusInternalServerError) return diff --git a/appview/pages/pages.go b/appview/pages/pages.go index 6825dc9f..a0a996dc 100644 --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -326,9 +326,8 @@ type NotificationsParams struct { LoggedInUser *oauth.User Notifications []*models.NotificationWithEntity UnreadCount int - HasMore bool - NextOffset int - Limit int + Page pagination.Page + Total int64 } func (p *Pages) Notifications(w io.Writer, params NotificationsParams) error { @@ -344,7 +343,7 @@ func (p *Pages) NotificationItem(w io.Writer, params NotificationItemParams) err } type NotificationCountParams struct { - Count int + Count int64 } func (p *Pages) NotificationCount(w io.Writer, params NotificationCountParams) error { diff --git a/appview/pages/templates/notifications/list.html b/appview/pages/templates/notifications/list.html index e44177ea..4e0ffd73 100644 --- a/appview/pages/templates/notifications/list.html +++ b/appview/pages/templates/notifications/list.html @@ -11,22 +11,55 @@ - {{if .Notifications}} -
- {{range .Notifications}} - {{template "notifications/fragments/item" .}} - {{end}} -
+ {{if .Notifications}} +
+ {{range .Notifications}} + {{template "notifications/fragments/item" .}} + {{end}} +
- {{else}} -
-
-
- {{ i "bell-off" "w-16 h-16" }} -
-

No notifications

-

When you receive notifications, they'll appear here.

+ {{else}} +
+
+
+ {{ i "bell-off" "w-16 h-16" }}
+

No notifications

+

When you receive notifications, they'll appear here.

- {{end}} +
+ {{end}} + + {{ template "pagination" . }} +{{ end }} + +{{ define "pagination" }} +
+ {{ if gt .Page.Offset 0 }} + {{ $prev := .Page.Previous }} + + {{ i "chevron-left" "w-4 h-4" }} + previous + + {{ else }} +
+ {{ end }} + + {{ $next := .Page.Next }} + {{ if lt $next.Offset .Total }} + {{ $next := .Page.Next }} + + next + {{ i "chevron-right" "w-4 h-4" }} + + {{ end }} +
{{ end }} diff --git a/appview/pagination/page.go b/appview/pagination/page.go index e9f47fa1..b3dc781b 100644 --- a/appview/pagination/page.go +++ b/appview/pagination/page.go @@ -8,7 +8,7 @@ type Page struct { func FirstPage() Page { return Page{ Offset: 0, - Limit: 10, + Limit: 30, } } -- 2.43.0