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