1package notifications
2
3import (
4 "log"
5 "net/http"
6 "strconv"
7
8 "github.com/go-chi/chi/v5"
9 "tangled.org/core/appview/db"
10 "tangled.org/core/appview/middleware"
11 "tangled.org/core/appview/oauth"
12 "tangled.org/core/appview/pages"
13 "tangled.org/core/appview/pagination"
14)
15
16type Notifications struct {
17 db *db.DB
18 oauth *oauth.OAuth
19 pages *pages.Pages
20}
21
22func New(database *db.DB, oauthHandler *oauth.OAuth, pagesHandler *pages.Pages) *Notifications {
23 return &Notifications{
24 db: database,
25 oauth: oauthHandler,
26 pages: pagesHandler,
27 }
28}
29
30func (n *Notifications) Router(mw *middleware.Middleware) http.Handler {
31 r := chi.NewRouter()
32
33 r.Use(middleware.AuthMiddleware(n.oauth))
34
35 r.Get("/", n.notificationsPage)
36
37 r.Get("/count", n.getUnreadCount)
38 r.Post("/{id}/read", n.markRead)
39 r.Post("/read-all", n.markAllRead)
40 r.Delete("/{id}", n.deleteNotification)
41
42 return r
43}
44
45func (n *Notifications) notificationsPage(w http.ResponseWriter, r *http.Request) {
46 userDid := n.oauth.GetDid(r)
47
48 limitStr := r.URL.Query().Get("limit")
49 offsetStr := r.URL.Query().Get("offset")
50
51 limit := 20 // default
52 if limitStr != "" {
53 if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 100 {
54 limit = l
55 }
56 }
57
58 offset := 0 // default
59 if offsetStr != "" {
60 if o, err := strconv.Atoi(offsetStr); err == nil && o >= 0 {
61 offset = o
62 }
63 }
64
65 page := pagination.Page{Limit: limit + 1, Offset: offset}
66 notifications, err := db.GetNotificationsWithEntities(n.db, page, db.FilterEq("recipient_did", userDid))
67 if err != nil {
68 log.Println("failed to get notifications:", err)
69 n.pages.Error500(w)
70 return
71 }
72
73 hasMore := len(notifications) > limit
74 if hasMore {
75 notifications = notifications[:limit]
76 }
77
78 err = n.db.MarkAllNotificationsRead(r.Context(), userDid)
79 if err != nil {
80 log.Println("failed to mark notifications as read:", err)
81 }
82
83 unreadCount := 0
84
85 user := n.oauth.GetUser(r)
86 if user == nil {
87 http.Error(w, "Failed to get user", http.StatusInternalServerError)
88 return
89 }
90
91 params := pages.NotificationsParams{
92 LoggedInUser: user,
93 Notifications: notifications,
94 UnreadCount: unreadCount,
95 HasMore: hasMore,
96 NextOffset: offset + limit,
97 Limit: limit,
98 }
99
100 err = n.pages.Notifications(w, params)
101 if err != nil {
102 log.Println("failed to load notifs:", err)
103 n.pages.Error500(w)
104 return
105 }
106}
107
108func (n *Notifications) getUnreadCount(w http.ResponseWriter, r *http.Request) {
109 userDid := n.oauth.GetDid(r)
110
111 count, err := n.db.GetUnreadNotificationCount(r.Context(), userDid)
112 if err != nil {
113 http.Error(w, "Failed to get unread count", http.StatusInternalServerError)
114 return
115 }
116
117 params := pages.NotificationCountParams{
118 Count: count,
119 }
120 err = n.pages.NotificationCount(w, params)
121 if err != nil {
122 http.Error(w, "Failed to render count", http.StatusInternalServerError)
123 return
124 }
125}
126
127func (n *Notifications) markRead(w http.ResponseWriter, r *http.Request) {
128 userDid := n.oauth.GetDid(r)
129
130 idStr := chi.URLParam(r, "id")
131 notificationID, err := strconv.ParseInt(idStr, 10, 64)
132 if err != nil {
133 http.Error(w, "Invalid notification ID", http.StatusBadRequest)
134 return
135 }
136
137 err = n.db.MarkNotificationRead(r.Context(), notificationID, userDid)
138 if err != nil {
139 http.Error(w, "Failed to mark notification as read", http.StatusInternalServerError)
140 return
141 }
142
143 w.WriteHeader(http.StatusNoContent)
144}
145
146func (n *Notifications) markAllRead(w http.ResponseWriter, r *http.Request) {
147 userDid := n.oauth.GetDid(r)
148
149 err := n.db.MarkAllNotificationsRead(r.Context(), userDid)
150 if err != nil {
151 http.Error(w, "Failed to mark all notifications as read", http.StatusInternalServerError)
152 return
153 }
154
155 http.Redirect(w, r, "/notifications", http.StatusSeeOther)
156}
157
158func (n *Notifications) deleteNotification(w http.ResponseWriter, r *http.Request) {
159 userDid := n.oauth.GetDid(r)
160
161 idStr := chi.URLParam(r, "id")
162 notificationID, err := strconv.ParseInt(idStr, 10, 64)
163 if err != nil {
164 http.Error(w, "Invalid notification ID", http.StatusBadRequest)
165 return
166 }
167
168 err = n.db.DeleteNotification(r.Context(), notificationID, userDid)
169 if err != nil {
170 http.Error(w, "Failed to delete notification", http.StatusInternalServerError)
171 return
172 }
173
174 w.WriteHeader(http.StatusOK)
175}