Monorepo for Tangled tangled.org

appview/db: simplify db handlers for notifications

Signed-off-by: oppiliappan <me@oppi.li>

oppi.li a4721e14 80d61c06

verified
Changed files
+125 -73
appview
db
models
notifications
notify
db
settings
-1
appview/db/artifact.go
··· 67 67 ) 68 68 69 69 rows, err := e.Query(query, args...) 70 - 71 70 if err != nil { 72 71 return nil, err 73 72 }
+81 -45
appview/db/notifications.go
··· 8 8 "strings" 9 9 "time" 10 10 11 + "github.com/bluesky-social/indigo/atproto/syntax" 11 12 "tangled.org/core/appview/models" 12 13 "tangled.org/core/appview/pagination" 13 14 ) 14 15 15 - func (d *DB) CreateNotification(ctx context.Context, notification *models.Notification) error { 16 + func CreateNotification(e Execer, notification *models.Notification) error { 16 17 query := ` 17 18 INSERT INTO notifications (recipient_did, actor_did, type, entity_type, entity_id, read, repo_id, issue_id, pull_id) 18 19 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) 19 20 ` 20 21 21 - result, err := d.DB.ExecContext(ctx, query, 22 + result, err := e.Exec(query, 22 23 notification.RecipientDid, 23 24 notification.ActorDid, 24 25 string(notification.Type), ··· 274 275 return count, nil 275 276 } 276 277 277 - func (d *DB) MarkNotificationRead(ctx context.Context, notificationID int64, userDID string) error { 278 + func MarkNotificationRead(e Execer, notificationID int64, userDID string) error { 278 279 idFilter := FilterEq("id", notificationID) 279 280 recipientFilter := FilterEq("recipient_did", userDID) 280 281 ··· 286 287 287 288 args := append(idFilter.Arg(), recipientFilter.Arg()...) 288 289 289 - result, err := d.DB.ExecContext(ctx, query, args...) 290 + result, err := e.Exec(query, args...) 290 291 if err != nil { 291 292 return fmt.Errorf("failed to mark notification as read: %w", err) 292 293 } ··· 303 304 return nil 304 305 } 305 306 306 - func (d *DB) MarkAllNotificationsRead(ctx context.Context, userDID string) error { 307 + func MarkAllNotificationsRead(e Execer, userDID string) error { 307 308 recipientFilter := FilterEq("recipient_did", userDID) 308 309 readFilter := FilterEq("read", 0) 309 310 ··· 315 316 316 317 args := append(recipientFilter.Arg(), readFilter.Arg()...) 317 318 318 - _, err := d.DB.ExecContext(ctx, query, args...) 319 + _, err := e.Exec(query, args...) 319 320 if err != nil { 320 321 return fmt.Errorf("failed to mark all notifications as read: %w", err) 321 322 } ··· 323 324 return nil 324 325 } 325 326 326 - func (d *DB) DeleteNotification(ctx context.Context, notificationID int64, userDID string) error { 327 + func DeleteNotification(e Execer, notificationID int64, userDID string) error { 327 328 idFilter := FilterEq("id", notificationID) 328 329 recipientFilter := FilterEq("recipient_did", userDID) 329 330 ··· 334 335 335 336 args := append(idFilter.Arg(), recipientFilter.Arg()...) 336 337 337 - result, err := d.DB.ExecContext(ctx, query, args...) 338 + result, err := e.Exec(query, args...) 338 339 if err != nil { 339 340 return fmt.Errorf("failed to delete notification: %w", err) 340 341 } ··· 351 352 return nil 352 353 } 353 354 354 - func (d *DB) GetNotificationPreferences(ctx context.Context, userDID string) (*models.NotificationPreferences, error) { 355 - userFilter := FilterEq("user_did", userDID) 355 + func GetNotificationPreference(e Execer, userDid string) (*models.NotificationPreferences, error) { 356 + prefs, err := GetNotificationPreferences(e, FilterEq("user_did", userDid)) 357 + if err != nil { 358 + return nil, err 359 + } 360 + 361 + p, ok := prefs[syntax.DID(userDid)] 362 + if !ok { 363 + return models.DefaultNotificationPreferences(syntax.DID(userDid)), nil 364 + } 365 + 366 + return p, nil 367 + } 368 + 369 + func GetNotificationPreferences(e Execer, filters ...filter) (map[syntax.DID]*models.NotificationPreferences, error) { 370 + prefsMap := make(map[syntax.DID]*models.NotificationPreferences) 371 + 372 + var conditions []string 373 + var args []any 374 + for _, filter := range filters { 375 + conditions = append(conditions, filter.Condition()) 376 + args = append(args, filter.Arg()...) 377 + } 378 + 379 + whereClause := "" 380 + if conditions != nil { 381 + whereClause = " where " + strings.Join(conditions, " and ") 382 + } 356 383 357 384 query := fmt.Sprintf(` 358 - SELECT id, user_did, repo_starred, issue_created, issue_commented, pull_created, 359 - pull_commented, followed, pull_merged, issue_closed, email_notifications 360 - FROM notification_preferences 361 - WHERE %s 362 - `, userFilter.Condition()) 385 + select 386 + id, 387 + user_did, 388 + repo_starred, 389 + issue_created, 390 + issue_commented, 391 + pull_created, 392 + pull_commented, 393 + followed, 394 + pull_merged, 395 + issue_closed, 396 + email_notifications 397 + from 398 + notification_preferences 399 + %s 400 + `, whereClause) 363 401 364 - var prefs models.NotificationPreferences 365 - err := d.DB.QueryRowContext(ctx, query, userFilter.Arg()...).Scan( 366 - &prefs.ID, 367 - &prefs.UserDid, 368 - &prefs.RepoStarred, 369 - &prefs.IssueCreated, 370 - &prefs.IssueCommented, 371 - &prefs.PullCreated, 372 - &prefs.PullCommented, 373 - &prefs.Followed, 374 - &prefs.PullMerged, 375 - &prefs.IssueClosed, 376 - &prefs.EmailNotifications, 377 - ) 378 - 402 + rows, err := e.Query(query, args...) 379 403 if err != nil { 380 - if err == sql.ErrNoRows { 381 - return &models.NotificationPreferences{ 382 - UserDid: userDID, 383 - RepoStarred: true, 384 - IssueCreated: true, 385 - IssueCommented: true, 386 - PullCreated: true, 387 - PullCommented: true, 388 - Followed: true, 389 - PullMerged: true, 390 - IssueClosed: true, 391 - EmailNotifications: false, 392 - }, nil 404 + return nil, err 405 + } 406 + defer rows.Close() 407 + 408 + for rows.Next() { 409 + var prefs models.NotificationPreferences 410 + if err := rows.Scan( 411 + &prefs.ID, 412 + &prefs.UserDid, 413 + &prefs.RepoStarred, 414 + &prefs.IssueCreated, 415 + &prefs.IssueCommented, 416 + &prefs.PullCreated, 417 + &prefs.PullCommented, 418 + &prefs.Followed, 419 + &prefs.PullMerged, 420 + &prefs.IssueClosed, 421 + &prefs.EmailNotifications, 422 + ); err != nil { 423 + return nil, err 393 424 } 394 - return nil, fmt.Errorf("failed to get notification preferences: %w", err) 425 + 426 + prefsMap[prefs.UserDid] = &prefs 427 + } 428 + 429 + if err := rows.Err(); err != nil { 430 + return nil, err 395 431 } 396 432 397 - return &prefs, nil 433 + return prefsMap, nil 398 434 } 399 435 400 436 func (d *DB) UpdateNotificationPreferences(ctx context.Context, prefs *models.NotificationPreferences) error {
+17 -1
appview/models/notifications.go
··· 2 2 3 3 import ( 4 4 "time" 5 + 6 + "github.com/bluesky-social/indigo/atproto/syntax" 5 7 ) 6 8 7 9 type NotificationType string ··· 69 71 70 72 type NotificationPreferences struct { 71 73 ID int64 72 - UserDid string 74 + UserDid syntax.DID 73 75 RepoStarred bool 74 76 IssueCreated bool 75 77 IssueCommented bool ··· 80 82 IssueClosed bool 81 83 EmailNotifications bool 82 84 } 85 + func DefaultNotificationPreferences(user syntax.DID) *NotificationPreferences { 86 + return &NotificationPreferences{ 87 + UserDid: user, 88 + RepoStarred: true, 89 + IssueCreated: true, 90 + IssueCommented: true, 91 + PullCreated: true, 92 + PullCommented: true, 93 + Followed: true, 94 + PullMerged: true, 95 + IssueClosed: true, 96 + EmailNotifications: false, 97 + } 98 + }
+4 -4
appview/notifications/notifications.go
··· 76 76 return 77 77 } 78 78 79 - err = n.db.MarkAllNotificationsRead(r.Context(), user.Did) 79 + err = db.MarkAllNotificationsRead(n.db, user.Did) 80 80 if err != nil { 81 81 l.Error("failed to mark notifications as read", "err", err) 82 82 } ··· 128 128 return 129 129 } 130 130 131 - err = n.db.MarkNotificationRead(r.Context(), notificationID, userDid) 131 + err = db.MarkNotificationRead(n.db, notificationID, userDid) 132 132 if err != nil { 133 133 http.Error(w, "Failed to mark notification as read", http.StatusInternalServerError) 134 134 return ··· 140 140 func (n *Notifications) markAllRead(w http.ResponseWriter, r *http.Request) { 141 141 userDid := n.oauth.GetDid(r) 142 142 143 - err := n.db.MarkAllNotificationsRead(r.Context(), userDid) 143 + err := db.MarkAllNotificationsRead(n.db, userDid) 144 144 if err != nil { 145 145 http.Error(w, "Failed to mark all notifications as read", http.StatusInternalServerError) 146 146 return ··· 159 159 return 160 160 } 161 161 162 - err = n.db.DeleteNotification(r.Context(), notificationID, userDid) 162 + err = db.DeleteNotification(n.db, notificationID, userDid) 163 163 if err != nil { 164 164 http.Error(w, "Failed to delete notification", http.StatusInternalServerError) 165 165 return
+20 -20
appview/notify/db/db.go
··· 42 42 } 43 43 44 44 // check if user wants these notifications 45 - prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did) 45 + prefs, err := db.GetNotificationPreference(n.db, repo.Did) 46 46 if err != nil { 47 47 log.Printf("NewStar: failed to get notification preferences for %s: %v", repo.Did, err) 48 48 return ··· 59 59 EntityId: string(star.RepoAt), 60 60 RepoId: &repo.Id, 61 61 } 62 - err = n.db.CreateNotification(ctx, notification) 62 + err = db.CreateNotification(n.db, notification) 63 63 if err != nil { 64 64 log.Printf("NewStar: failed to create notification: %v", err) 65 65 return ··· 81 81 return 82 82 } 83 83 84 - prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did) 84 + prefs, err := db.GetNotificationPreference(n.db, repo.Did) 85 85 if err != nil { 86 86 log.Printf("NewIssue: failed to get notification preferences for %s: %v", repo.Did, err) 87 87 return ··· 100 100 IssueId: &issue.Id, 101 101 } 102 102 103 - err = n.db.CreateNotification(ctx, notification) 103 + err = db.CreateNotification(n.db, notification) 104 104 if err != nil { 105 105 log.Printf("NewIssue: failed to create notification: %v", err) 106 106 return ··· 129 129 130 130 // notify issue author (if not the commenter) 131 131 if issue.Did != comment.Did { 132 - prefs, err := n.db.GetNotificationPreferences(ctx, issue.Did) 132 + prefs, err := db.GetNotificationPreference(n.db, issue.Did) 133 133 if err == nil && prefs.IssueCommented { 134 134 recipients[issue.Did] = true 135 135 } else if err != nil { ··· 139 139 140 140 // notify repo owner (if not the commenter and not already added) 141 141 if repo.Did != comment.Did && repo.Did != issue.Did { 142 - prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did) 142 + prefs, err := db.GetNotificationPreference(n.db, repo.Did) 143 143 if err == nil && prefs.IssueCommented { 144 144 recipients[repo.Did] = true 145 145 } else if err != nil { ··· 159 159 IssueId: &issue.Id, 160 160 } 161 161 162 - err = n.db.CreateNotification(ctx, notification) 162 + err = db.CreateNotification(n.db, notification) 163 163 if err != nil { 164 164 log.Printf("NewIssueComment: failed to create notification for %s: %v", recipientDid, err) 165 165 } ··· 167 167 } 168 168 169 169 func (n *databaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) { 170 - prefs, err := n.db.GetNotificationPreferences(ctx, follow.SubjectDid) 170 + prefs, err := db.GetNotificationPreference(n.db, follow.SubjectDid) 171 171 if err != nil { 172 172 log.Printf("NewFollow: failed to get notification preferences for %s: %v", follow.SubjectDid, err) 173 173 return ··· 184 184 EntityId: follow.UserDid, 185 185 } 186 186 187 - err = n.db.CreateNotification(ctx, notification) 187 + err = db.CreateNotification(n.db, notification) 188 188 if err != nil { 189 189 log.Printf("NewFollow: failed to create notification: %v", err) 190 190 return ··· 206 206 return 207 207 } 208 208 209 - prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did) 209 + prefs, err := db.GetNotificationPreference(n.db, repo.Did) 210 210 if err != nil { 211 211 log.Printf("NewPull: failed to get notification preferences for %s: %v", repo.Did, err) 212 212 return ··· 225 225 PullId: func() *int64 { id := int64(pull.ID); return &id }(), 226 226 } 227 227 228 - err = n.db.CreateNotification(ctx, notification) 228 + err = db.CreateNotification(n.db, notification) 229 229 if err != nil { 230 230 log.Printf("NewPull: failed to create notification: %v", err) 231 231 return ··· 256 256 257 257 // notify pull request author (if not the commenter) 258 258 if pull.OwnerDid != comment.OwnerDid { 259 - prefs, err := n.db.GetNotificationPreferences(ctx, pull.OwnerDid) 259 + prefs, err := db.GetNotificationPreference(n.db, pull.OwnerDid) 260 260 if err == nil && prefs.PullCommented { 261 261 recipients[pull.OwnerDid] = true 262 262 } else if err != nil { ··· 266 266 267 267 // notify repo owner (if not the commenter and not already added) 268 268 if repo.Did != comment.OwnerDid && repo.Did != pull.OwnerDid { 269 - prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did) 269 + prefs, err := db.GetNotificationPreference(n.db, repo.Did) 270 270 if err == nil && prefs.PullCommented { 271 271 recipients[repo.Did] = true 272 272 } else if err != nil { ··· 285 285 PullId: func() *int64 { id := int64(pull.ID); return &id }(), 286 286 } 287 287 288 - err = n.db.CreateNotification(ctx, notification) 288 + err = db.CreateNotification(n.db, notification) 289 289 if err != nil { 290 290 log.Printf("NewPullComment: failed to create notification for %s: %v", recipientDid, err) 291 291 } ··· 322 322 } 323 323 324 324 // Check if user wants these notifications 325 - prefs, err := n.db.GetNotificationPreferences(ctx, repo.Did) 325 + prefs, err := db.GetNotificationPreference(n.db, repo.Did) 326 326 if err != nil { 327 327 log.Printf("NewIssueClosed: failed to get notification preferences for %s: %v", repo.Did, err) 328 328 return ··· 341 341 IssueId: &issue.Id, 342 342 } 343 343 344 - err = n.db.CreateNotification(ctx, notification) 344 + err = db.CreateNotification(n.db, notification) 345 345 if err != nil { 346 346 log.Printf("NewIssueClosed: failed to create notification: %v", err) 347 347 return ··· 362 362 } 363 363 364 364 // Check if user wants these notifications 365 - prefs, err := n.db.GetNotificationPreferences(ctx, pull.OwnerDid) 365 + prefs, err := db.GetNotificationPreference(n.db, pull.OwnerDid) 366 366 if err != nil { 367 367 log.Printf("NewPullMerged: failed to get notification preferences for %s: %v", pull.OwnerDid, err) 368 368 return ··· 381 381 PullId: func() *int64 { id := int64(pull.ID); return &id }(), 382 382 } 383 383 384 - err = n.db.CreateNotification(ctx, notification) 384 + err = db.CreateNotification(n.db, notification) 385 385 if err != nil { 386 386 log.Printf("NewPullMerged: failed to create notification: %v", err) 387 387 return ··· 402 402 } 403 403 404 404 // Check if user wants these notifications - reuse pull_merged preference for now 405 - prefs, err := n.db.GetNotificationPreferences(ctx, pull.OwnerDid) 405 + prefs, err := db.GetNotificationPreference(n.db, pull.OwnerDid) 406 406 if err != nil { 407 407 log.Printf("NewPullClosed: failed to get notification preferences for %s: %v", pull.OwnerDid, err) 408 408 return ··· 421 421 PullId: func() *int64 { id := int64(pull.ID); return &id }(), 422 422 } 423 423 424 - err = n.db.CreateNotification(ctx, notification) 424 + err = db.CreateNotification(n.db, notification) 425 425 if err != nil { 426 426 log.Printf("NewPullClosed: failed to create notification: %v", err) 427 427 return
+3 -2
appview/settings/settings.go
··· 22 22 "tangled.org/core/tid" 23 23 24 24 comatproto "github.com/bluesky-social/indigo/api/atproto" 25 + "github.com/bluesky-social/indigo/atproto/syntax" 25 26 lexutil "github.com/bluesky-social/indigo/lex/util" 26 27 "github.com/gliderlabs/ssh" 27 28 "github.com/google/uuid" ··· 91 92 user := s.OAuth.GetUser(r) 92 93 did := s.OAuth.GetDid(r) 93 94 94 - prefs, err := s.Db.GetNotificationPreferences(r.Context(), did) 95 + prefs, err := db.GetNotificationPreference(s.Db, did) 95 96 if err != nil { 96 97 log.Printf("failed to get notification preferences: %s", err) 97 98 s.Pages.Notice(w, "settings-notifications-error", "Unable to load notification preferences.") ··· 110 111 did := s.OAuth.GetDid(r) 111 112 112 113 prefs := &models.NotificationPreferences{ 113 - UserDid: did, 114 + UserDid: syntax.DID(did), 114 115 RepoStarred: r.FormValue("repo_starred") == "on", 115 116 IssueCreated: r.FormValue("issue_created") == "on", 116 117 IssueCommented: r.FormValue("issue_commented") == "on",