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