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