From a4721e14f39f935619b238d6b837bf98e05704ca Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Wed, 15 Oct 2025 17:43:25 +0100 Subject: [PATCH] appview/db: simplify db handlers for notifications Change-Id: rxklxxstzmrzxtpzqkwqyutqrwnnryml Signed-off-by: oppiliappan --- appview/db/artifact.go | 1 - appview/db/notifications.go | 126 ++++++++++++++++--------- appview/models/notifications.go | 18 +++- appview/notifications/notifications.go | 8 +- appview/notify/db/db.go | 40 ++++---- appview/settings/settings.go | 5 +- 6 files changed, 125 insertions(+), 73 deletions(-) diff --git a/appview/db/artifact.go b/appview/db/artifact.go index 407ff3a3..804dbe92 100644 --- a/appview/db/artifact.go +++ b/appview/db/artifact.go @@ -67,7 +67,6 @@ func GetArtifact(e Execer, filters ...filter) ([]models.Artifact, error) { ) rows, err := e.Query(query, args...) - if err != nil { return nil, err } diff --git a/appview/db/notifications.go b/appview/db/notifications.go index 6282ef56..483f3714 100644 --- a/appview/db/notifications.go +++ b/appview/db/notifications.go @@ -8,17 +8,18 @@ import ( "strings" "time" + "github.com/bluesky-social/indigo/atproto/syntax" "tangled.org/core/appview/models" "tangled.org/core/appview/pagination" ) -func (d *DB) CreateNotification(ctx context.Context, notification *models.Notification) error { +func CreateNotification(e Execer, notification *models.Notification) error { query := ` INSERT INTO notifications (recipient_did, actor_did, type, entity_type, entity_id, read, repo_id, issue_id, pull_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ` - result, err := d.DB.ExecContext(ctx, query, + result, err := e.Exec(query, notification.RecipientDid, notification.ActorDid, string(notification.Type), @@ -274,7 +275,7 @@ func CountNotifications(e Execer, filters ...filter) (int64, error) { return count, nil } -func (d *DB) MarkNotificationRead(ctx context.Context, notificationID int64, userDID string) error { +func MarkNotificationRead(e Execer, notificationID int64, userDID string) error { idFilter := FilterEq("id", notificationID) recipientFilter := FilterEq("recipient_did", userDID) @@ -286,7 +287,7 @@ func (d *DB) MarkNotificationRead(ctx context.Context, notificationID int64, use args := append(idFilter.Arg(), recipientFilter.Arg()...) - result, err := d.DB.ExecContext(ctx, query, args...) + result, err := e.Exec(query, args...) if err != nil { return fmt.Errorf("failed to mark notification as read: %w", err) } @@ -303,7 +304,7 @@ func (d *DB) MarkNotificationRead(ctx context.Context, notificationID int64, use return nil } -func (d *DB) MarkAllNotificationsRead(ctx context.Context, userDID string) error { +func MarkAllNotificationsRead(e Execer, userDID string) error { recipientFilter := FilterEq("recipient_did", userDID) readFilter := FilterEq("read", 0) @@ -315,7 +316,7 @@ func (d *DB) MarkAllNotificationsRead(ctx context.Context, userDID string) error args := append(recipientFilter.Arg(), readFilter.Arg()...) - _, err := d.DB.ExecContext(ctx, query, args...) + _, err := e.Exec(query, args...) if err != nil { return fmt.Errorf("failed to mark all notifications as read: %w", err) } @@ -323,7 +324,7 @@ func (d *DB) MarkAllNotificationsRead(ctx context.Context, userDID string) error return nil } -func (d *DB) DeleteNotification(ctx context.Context, notificationID int64, userDID string) error { +func DeleteNotification(e Execer, notificationID int64, userDID string) error { idFilter := FilterEq("id", notificationID) recipientFilter := FilterEq("recipient_did", userDID) @@ -334,7 +335,7 @@ func (d *DB) DeleteNotification(ctx context.Context, notificationID int64, userD args := append(idFilter.Arg(), recipientFilter.Arg()...) - result, err := d.DB.ExecContext(ctx, query, args...) + result, err := e.Exec(query, args...) if err != nil { return fmt.Errorf("failed to delete notification: %w", err) } @@ -351,50 +352,85 @@ func (d *DB) DeleteNotification(ctx context.Context, notificationID int64, userD return nil } -func (d *DB) GetNotificationPreferences(ctx context.Context, userDID string) (*models.NotificationPreferences, error) { - userFilter := FilterEq("user_did", userDID) +func GetNotificationPreference(e Execer, userDid string) (*models.NotificationPreferences, error) { + prefs, err := GetNotificationPreferences(e, FilterEq("user_did", userDid)) + if err != nil { + return nil, err + } + + p, ok := prefs[syntax.DID(userDid)] + if !ok { + return models.DefaultNotificationPreferences(syntax.DID(userDid)), nil + } + + return p, nil +} + +func GetNotificationPreferences(e Execer, filters ...filter) (map[syntax.DID]*models.NotificationPreferences, error) { + prefsMap := make(map[syntax.DID]*models.NotificationPreferences) + + var conditions []string + var args []any + for _, filter := range filters { + conditions = append(conditions, filter.Condition()) + args = append(args, filter.Arg()...) + } + + whereClause := "" + if conditions != nil { + whereClause = " where " + strings.Join(conditions, " and ") + } query := fmt.Sprintf(` - SELECT id, user_did, repo_starred, issue_created, issue_commented, pull_created, - pull_commented, followed, pull_merged, issue_closed, email_notifications - FROM notification_preferences - WHERE %s - `, userFilter.Condition()) - - var prefs models.NotificationPreferences - err := d.DB.QueryRowContext(ctx, query, userFilter.Arg()...).Scan( - &prefs.ID, - &prefs.UserDid, - &prefs.RepoStarred, - &prefs.IssueCreated, - &prefs.IssueCommented, - &prefs.PullCreated, - &prefs.PullCommented, - &prefs.Followed, - &prefs.PullMerged, - &prefs.IssueClosed, - &prefs.EmailNotifications, - ) + select + id, + user_did, + repo_starred, + issue_created, + issue_commented, + pull_created, + pull_commented, + followed, + pull_merged, + issue_closed, + email_notifications + from + notification_preferences + %s + `, whereClause) + rows, err := e.Query(query, args...) if err != nil { - if err == sql.ErrNoRows { - return &models.NotificationPreferences{ - UserDid: userDID, - RepoStarred: true, - IssueCreated: true, - IssueCommented: true, - PullCreated: true, - PullCommented: true, - Followed: true, - PullMerged: true, - IssueClosed: true, - EmailNotifications: false, - }, nil + return nil, err + } + defer rows.Close() + + for rows.Next() { + var prefs models.NotificationPreferences + if err := rows.Scan( + &prefs.ID, + &prefs.UserDid, + &prefs.RepoStarred, + &prefs.IssueCreated, + &prefs.IssueCommented, + &prefs.PullCreated, + &prefs.PullCommented, + &prefs.Followed, + &prefs.PullMerged, + &prefs.IssueClosed, + &prefs.EmailNotifications, + ); err != nil { + return nil, err } - return nil, fmt.Errorf("failed to get notification preferences: %w", err) + + prefsMap[prefs.UserDid] = &prefs + } + + if err := rows.Err(); err != nil { + return nil, err } - return &prefs, nil + return prefsMap, nil } func (d *DB) UpdateNotificationPreferences(ctx context.Context, prefs *models.NotificationPreferences) error { diff --git a/appview/models/notifications.go b/appview/models/notifications.go index 441f3df5..6e9d5754 100644 --- a/appview/models/notifications.go +++ b/appview/models/notifications.go @@ -2,6 +2,8 @@ package models import ( "time" + + "github.com/bluesky-social/indigo/atproto/syntax" ) type NotificationType string @@ -69,7 +71,7 @@ type NotificationWithEntity struct { type NotificationPreferences struct { ID int64 - UserDid string + UserDid syntax.DID RepoStarred bool IssueCreated bool IssueCommented bool @@ -80,3 +82,17 @@ type NotificationPreferences struct { IssueClosed bool EmailNotifications bool } +func DefaultNotificationPreferences(user syntax.DID) *NotificationPreferences { + return &NotificationPreferences{ + UserDid: user, + RepoStarred: true, + IssueCreated: true, + IssueCommented: true, + PullCreated: true, + PullCommented: true, + Followed: true, + PullMerged: true, + IssueClosed: true, + EmailNotifications: false, + } +} diff --git a/appview/notifications/notifications.go b/appview/notifications/notifications.go index 01ff59af..a4047521 100644 --- a/appview/notifications/notifications.go +++ b/appview/notifications/notifications.go @@ -76,7 +76,7 @@ func (n *Notifications) notificationsPage(w http.ResponseWriter, r *http.Request return } - err = n.db.MarkAllNotificationsRead(r.Context(), user.Did) + err = db.MarkAllNotificationsRead(n.db, user.Did) if err != nil { l.Error("failed to mark notifications as read", "err", err) } @@ -128,7 +128,7 @@ func (n *Notifications) markRead(w http.ResponseWriter, r *http.Request) { return } - err = n.db.MarkNotificationRead(r.Context(), notificationID, userDid) + err = db.MarkNotificationRead(n.db, notificationID, userDid) if err != nil { http.Error(w, "Failed to mark notification as read", http.StatusInternalServerError) return @@ -140,7 +140,7 @@ func (n *Notifications) markRead(w http.ResponseWriter, r *http.Request) { func (n *Notifications) markAllRead(w http.ResponseWriter, r *http.Request) { userDid := n.oauth.GetDid(r) - err := n.db.MarkAllNotificationsRead(r.Context(), userDid) + err := db.MarkAllNotificationsRead(n.db, userDid) if err != nil { http.Error(w, "Failed to mark all notifications as read", http.StatusInternalServerError) return @@ -159,7 +159,7 @@ func (n *Notifications) deleteNotification(w http.ResponseWriter, r *http.Reques return } - err = n.db.DeleteNotification(r.Context(), notificationID, userDid) + err = db.DeleteNotification(n.db, notificationID, userDid) if err != nil { http.Error(w, "Failed to delete notification", http.StatusInternalServerError) return diff --git a/appview/notify/db/db.go b/appview/notify/db/db.go index 900428ea..66174186 100644 --- a/appview/notify/db/db.go +++ b/appview/notify/db/db.go @@ -42,7 +42,7 @@ func (n *databaseNotifier) NewStar(ctx context.Context, star *models.Star) { } // check if user wants these notifications - prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did) + prefs, err := db.GetNotificationPreference(n.db, repo.Did) if err != nil { log.Printf("NewStar: failed to get notification preferences for %s: %v", repo.Did, err) return @@ -59,7 +59,7 @@ func (n *databaseNotifier) NewStar(ctx context.Context, star *models.Star) { EntityId: string(star.RepoAt), RepoId: &repo.Id, } - err = n.db.CreateNotification(ctx, notification) + err = db.CreateNotification(n.db, notification) if err != nil { log.Printf("NewStar: failed to create notification: %v", err) return @@ -81,7 +81,7 @@ func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue) { return } - prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did) + prefs, err := db.GetNotificationPreference(n.db, repo.Did) if err != nil { log.Printf("NewIssue: failed to get notification preferences for %s: %v", repo.Did, err) return @@ -100,7 +100,7 @@ func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue) { IssueId: &issue.Id, } - err = n.db.CreateNotification(ctx, notification) + err = db.CreateNotification(n.db, notification) if err != nil { log.Printf("NewIssue: failed to create notification: %v", err) return @@ -129,7 +129,7 @@ func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models. // notify issue author (if not the commenter) if issue.Did != comment.Did { - prefs, err := n.db.GetNotificationPreferences(ctx, issue.Did) + prefs, err := db.GetNotificationPreference(n.db, issue.Did) if err == nil && prefs.IssueCommented { recipients[issue.Did] = true } else if err != nil { @@ -139,7 +139,7 @@ func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models. // notify repo owner (if not the commenter and not already added) if repo.Did != comment.Did && repo.Did != issue.Did { - prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did) + prefs, err := db.GetNotificationPreference(n.db, repo.Did) if err == nil && prefs.IssueCommented { recipients[repo.Did] = true } else if err != nil { @@ -159,7 +159,7 @@ func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models. IssueId: &issue.Id, } - err = n.db.CreateNotification(ctx, notification) + err = db.CreateNotification(n.db, notification) if err != nil { log.Printf("NewIssueComment: failed to create notification for %s: %v", recipientDid, err) } @@ -167,7 +167,7 @@ func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models. } func (n *databaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) { - prefs, err := n.db.GetNotificationPreferences(ctx, follow.SubjectDid) + prefs, err := db.GetNotificationPreference(n.db, follow.SubjectDid) if err != nil { log.Printf("NewFollow: failed to get notification preferences for %s: %v", follow.SubjectDid, err) return @@ -184,7 +184,7 @@ func (n *databaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) EntityId: follow.UserDid, } - err = n.db.CreateNotification(ctx, notification) + err = db.CreateNotification(n.db, notification) if err != nil { log.Printf("NewFollow: failed to create notification: %v", err) return @@ -206,7 +206,7 @@ func (n *databaseNotifier) NewPull(ctx context.Context, pull *models.Pull) { return } - prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did) + prefs, err := db.GetNotificationPreference(n.db, repo.Did) if err != nil { log.Printf("NewPull: failed to get notification preferences for %s: %v", repo.Did, err) return @@ -225,7 +225,7 @@ func (n *databaseNotifier) NewPull(ctx context.Context, pull *models.Pull) { PullId: func() *int64 { id := int64(pull.ID); return &id }(), } - err = n.db.CreateNotification(ctx, notification) + err = db.CreateNotification(n.db, notification) if err != nil { log.Printf("NewPull: failed to create notification: %v", err) return @@ -256,7 +256,7 @@ func (n *databaseNotifier) NewPullComment(ctx context.Context, comment *models.P // notify pull request author (if not the commenter) if pull.OwnerDid != comment.OwnerDid { - prefs, err := n.db.GetNotificationPreferences(ctx, pull.OwnerDid) + prefs, err := db.GetNotificationPreference(n.db, pull.OwnerDid) if err == nil && prefs.PullCommented { recipients[pull.OwnerDid] = true } else if err != nil { @@ -266,7 +266,7 @@ func (n *databaseNotifier) NewPullComment(ctx context.Context, comment *models.P // notify repo owner (if not the commenter and not already added) if repo.Did != comment.OwnerDid && repo.Did != pull.OwnerDid { - prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did) + prefs, err := db.GetNotificationPreference(n.db, repo.Did) if err == nil && prefs.PullCommented { recipients[repo.Did] = true } else if err != nil { @@ -285,7 +285,7 @@ func (n *databaseNotifier) NewPullComment(ctx context.Context, comment *models.P PullId: func() *int64 { id := int64(pull.ID); return &id }(), } - err = n.db.CreateNotification(ctx, notification) + err = db.CreateNotification(n.db, notification) if err != nil { log.Printf("NewPullComment: failed to create notification for %s: %v", recipientDid, err) } @@ -322,7 +322,7 @@ func (n *databaseNotifier) NewIssueClosed(ctx context.Context, issue *models.Iss } // Check if user wants these notifications - prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did) + prefs, err := db.GetNotificationPreference(n.db, repo.Did) if err != nil { log.Printf("NewIssueClosed: failed to get notification preferences for %s: %v", repo.Did, err) return @@ -341,7 +341,7 @@ func (n *databaseNotifier) NewIssueClosed(ctx context.Context, issue *models.Iss IssueId: &issue.Id, } - err = n.db.CreateNotification(ctx, notification) + err = db.CreateNotification(n.db, notification) if err != nil { log.Printf("NewIssueClosed: failed to create notification: %v", err) return @@ -362,7 +362,7 @@ func (n *databaseNotifier) NewPullMerged(ctx context.Context, pull *models.Pull) } // Check if user wants these notifications - prefs, err := n.db.GetNotificationPreferences(ctx, pull.OwnerDid) + prefs, err := db.GetNotificationPreference(n.db, pull.OwnerDid) if err != nil { log.Printf("NewPullMerged: failed to get notification preferences for %s: %v", pull.OwnerDid, err) return @@ -381,7 +381,7 @@ func (n *databaseNotifier) NewPullMerged(ctx context.Context, pull *models.Pull) PullId: func() *int64 { id := int64(pull.ID); return &id }(), } - err = n.db.CreateNotification(ctx, notification) + err = db.CreateNotification(n.db, notification) if err != nil { log.Printf("NewPullMerged: failed to create notification: %v", err) return @@ -402,7 +402,7 @@ func (n *databaseNotifier) NewPullClosed(ctx context.Context, pull *models.Pull) } // Check if user wants these notifications - reuse pull_merged preference for now - prefs, err := n.db.GetNotificationPreferences(ctx, pull.OwnerDid) + prefs, err := db.GetNotificationPreference(n.db, pull.OwnerDid) if err != nil { log.Printf("NewPullClosed: failed to get notification preferences for %s: %v", pull.OwnerDid, err) return @@ -421,7 +421,7 @@ func (n *databaseNotifier) NewPullClosed(ctx context.Context, pull *models.Pull) PullId: func() *int64 { id := int64(pull.ID); return &id }(), } - err = n.db.CreateNotification(ctx, notification) + err = db.CreateNotification(n.db, notification) if err != nil { log.Printf("NewPullClosed: failed to create notification: %v", err) return diff --git a/appview/settings/settings.go b/appview/settings/settings.go index 4dca2414..1f1d9187 100644 --- a/appview/settings/settings.go +++ b/appview/settings/settings.go @@ -22,6 +22,7 @@ import ( "tangled.org/core/tid" comatproto "github.com/bluesky-social/indigo/api/atproto" + "github.com/bluesky-social/indigo/atproto/syntax" lexutil "github.com/bluesky-social/indigo/lex/util" "github.com/gliderlabs/ssh" "github.com/google/uuid" @@ -91,7 +92,7 @@ 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) + prefs, err := db.GetNotificationPreference(s.Db, 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.") @@ -110,7 +111,7 @@ func (s *Settings) updateNotificationPreferences(w http.ResponseWriter, r *http. did := s.OAuth.GetDid(r) prefs := &models.NotificationPreferences{ - UserDid: did, + UserDid: syntax.DID(did), RepoStarred: r.FormValue("repo_starred") == "on", IssueCreated: r.FormValue("issue_created") == "on", IssueCommented: r.FormValue("issue_commented") == "on", -- 2.43.0