appview/notifications: handlers for notifs #594

merged
opened by anirudh.fi targeting master from push-xwotmtuuvokm
Changed files
+230
appview
notifications
settings
state
+173
appview/notifications/notifications.go
···
+
package notifications
+
+
import (
+
"log"
+
"net/http"
+
"strconv"
+
+
"github.com/go-chi/chi/v5"
+
"tangled.org/core/appview/db"
+
"tangled.org/core/appview/middleware"
+
"tangled.org/core/appview/oauth"
+
"tangled.org/core/appview/pages"
+
)
+
+
type Notifications struct {
+
db *db.DB
+
oauth *oauth.OAuth
+
pages *pages.Pages
+
}
+
+
func New(database *db.DB, oauthHandler *oauth.OAuth, pagesHandler *pages.Pages) *Notifications {
+
return &Notifications{
+
db: database,
+
oauth: oauthHandler,
+
pages: pagesHandler,
+
}
+
}
+
+
func (n *Notifications) Router(mw *middleware.Middleware) http.Handler {
+
r := chi.NewRouter()
+
+
r.Use(middleware.AuthMiddleware(n.oauth))
+
+
r.Get("/", n.notificationsPage)
+
+
r.Get("/count", n.getUnreadCount)
+
r.Post("/{id}/read", n.markRead)
+
r.Post("/read-all", n.markAllRead)
+
r.Delete("/{id}", n.deleteNotification)
+
+
return r
+
}
+
+
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
+
}
+
}
+
+
offset := 0 // default
+
if offsetStr != "" {
+
if o, err := strconv.Atoi(offsetStr); err == nil && o >= 0 {
+
offset = o
+
}
+
}
+
+
notifications, err := n.db.GetNotificationsWithEntities(r.Context(), userDid, limit+1, offset)
+
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)
+
}
+
+
unreadCount := 0
+
+
user := n.oauth.GetUser(r)
+
if user == nil {
+
http.Error(w, "Failed to get user", http.StatusInternalServerError)
+
return
+
}
+
+
params := 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
+
}
+
}
+
+
func (n *Notifications) getUnreadCount(w http.ResponseWriter, r *http.Request) {
+
userDid := n.oauth.GetDid(r)
+
+
count, err := n.db.GetUnreadNotificationCount(r.Context(), userDid)
+
if err != nil {
+
http.Error(w, "Failed to get unread count", http.StatusInternalServerError)
+
return
+
}
+
+
params := pages.NotificationCountParams{
+
Count: count,
+
}
+
err = n.pages.NotificationCount(w, params)
+
if err != nil {
+
http.Error(w, "Failed to render count", http.StatusInternalServerError)
+
return
+
}
+
}
+
+
func (n *Notifications) markRead(w http.ResponseWriter, r *http.Request) {
+
userDid := n.oauth.GetDid(r)
+
+
idStr := chi.URLParam(r, "id")
+
notificationID, err := strconv.ParseInt(idStr, 10, 64)
+
if err != nil {
+
http.Error(w, "Invalid notification ID", http.StatusBadRequest)
+
return
+
}
+
+
err = n.db.MarkNotificationRead(r.Context(), notificationID, userDid)
+
if err != nil {
+
http.Error(w, "Failed to mark notification as read", http.StatusInternalServerError)
+
return
+
}
+
+
w.WriteHeader(http.StatusNoContent)
+
}
+
+
func (n *Notifications) markAllRead(w http.ResponseWriter, r *http.Request) {
+
userDid := n.oauth.GetDid(r)
+
+
err := n.db.MarkAllNotificationsRead(r.Context(), userDid)
+
if err != nil {
+
http.Error(w, "Failed to mark all notifications as read", http.StatusInternalServerError)
+
return
+
}
+
+
http.Redirect(w, r, "/notifications", http.StatusSeeOther)
+
}
+
+
func (n *Notifications) deleteNotification(w http.ResponseWriter, r *http.Request) {
+
userDid := n.oauth.GetDid(r)
+
+
idStr := chi.URLParam(r, "id")
+
notificationID, err := strconv.ParseInt(idStr, 10, 64)
+
if err != nil {
+
http.Error(w, "Invalid notification ID", http.StatusBadRequest)
+
return
+
}
+
+
err = n.db.DeleteNotification(r.Context(), notificationID, userDid)
+
if err != nil {
+
http.Error(w, "Failed to delete notification", http.StatusInternalServerError)
+
return
+
}
+
+
w.WriteHeader(http.StatusOK)
+
}
+51
appview/settings/settings.go
···
{"Name": "profile", "Icon": "user"},
{"Name": "keys", "Icon": "key"},
{"Name": "emails", "Icon": "mail"},
+
{"Name": "notifications", "Icon": "bell"},
}
)
···
r.Post("/primary", s.emailsPrimary)
})
+
r.Route("/notifications", func(r chi.Router) {
+
r.Get("/", s.notificationsSettings)
+
r.Put("/", s.updateNotificationPreferences)
+
})
+
return r
}
···
})
}
+
func (s *Settings) notificationsSettings(w http.ResponseWriter, r *http.Request) {
+
user := s.OAuth.GetUser(r)
+
did := s.OAuth.GetDid(r)
+
+
prefs, err := s.Db.GetNotificationPreferences(r.Context(), did)
+
if err != nil {
+
log.Printf("failed to get notification preferences: %s", err)
+
s.Pages.Notice(w, "settings-notifications-error", "Unable to load notification preferences.")
+
return
+
}
+
+
s.Pages.UserNotificationSettings(w, pages.UserNotificationSettingsParams{
+
LoggedInUser: user,
+
Preferences: prefs,
+
Tabs: settingsTabs,
+
Tab: "notifications",
+
})
+
}
+
+
func (s *Settings) updateNotificationPreferences(w http.ResponseWriter, r *http.Request) {
+
did := s.OAuth.GetDid(r)
+
+
prefs := &models.NotificationPreferences{
+
UserDid: did,
+
RepoStarred: r.FormValue("repo_starred") == "on",
+
IssueCreated: r.FormValue("issue_created") == "on",
+
IssueCommented: r.FormValue("issue_commented") == "on",
+
IssueClosed: r.FormValue("issue_closed") == "on",
+
PullCreated: r.FormValue("pull_created") == "on",
+
PullCommented: r.FormValue("pull_commented") == "on",
+
PullMerged: r.FormValue("pull_merged") == "on",
+
Followed: r.FormValue("followed") == "on",
+
EmailNotifications: r.FormValue("email_notifications") == "on",
+
}
+
+
err := s.Db.UpdateNotificationPreferences(r.Context(), prefs)
+
if err != nil {
+
log.Printf("failed to update notification preferences: %s", err)
+
s.Pages.Notice(w, "settings-notifications-error", "Unable to save notification preferences.")
+
return
+
}
+
+
s.Pages.Notice(w, "settings-notifications-success", "Notification preferences saved successfully.")
+
}
+
func (s *Settings) keysSettings(w http.ResponseWriter, r *http.Request) {
user := s.OAuth.GetUser(r)
pubKeys, err := db.GetPublicKeysForDid(s.Db, user.Did)
+6
appview/state/router.go
···
"tangled.org/core/appview/knots"
"tangled.org/core/appview/labels"
"tangled.org/core/appview/middleware"
+
"tangled.org/core/appview/notifications"
oauthhandler "tangled.org/core/appview/oauth/handler"
"tangled.org/core/appview/pipelines"
"tangled.org/core/appview/pulls"
···
return ls.Router(mw)
}
+
func (s *State) NotificationsRouter(mw *middleware.Middleware) http.Handler {
+
notifs := notifications.New(s.db, s.oauth, s.pages)
+
return notifs.Router(mw)
+
}
+
func (s *State) SignupRouter() http.Handler {
logger := log.New("signup")