Yōten: A social tracker for your language learning journey built on the atproto.

feat: add notifications table to db

brookjeynes.dev 7a892a74 dce23911

verified
+143
+143
internal/db/notification.go
··· 1 + package db 2 + 3 + import ( 4 + "fmt" 5 + "time" 6 + 7 + "github.com/bluesky-social/indigo/atproto/syntax" 8 + ) 9 + 10 + type NotificationType string 11 + 12 + const ( 13 + NotificationTypeFollow NotificationType = "follow" 14 + NotificationTypeReaction NotificationType = "reaction" 15 + ) 16 + 17 + type NotificationState string 18 + 19 + const ( 20 + NotificationStateUnread NotificationState = "unread" 21 + NotificationStateRead NotificationState = "read" 22 + ) 23 + 24 + type NotificationWithBskyHandle struct { 25 + Notification 26 + ActorBskyHandle string 27 + } 28 + 29 + type Notification struct { 30 + ID int 31 + RecipientDid string 32 + ActorDid string 33 + SubjectRkey string 34 + State NotificationState 35 + Type NotificationType 36 + CreatedAt time.Time 37 + } 38 + 39 + func CreateNotification(e Execer, recipientDid, actorDid, subjectUri string, notificationType NotificationType) error { 40 + query := ` 41 + insert into notifications 42 + (recipient_did, actor_did, subject_uri, type) 43 + values (?, ?, ?, ?) 44 + ` 45 + 46 + _, err := e.Exec(query, recipientDid, actorDid, subjectUri, notificationType) 47 + if err != nil { 48 + return fmt.Errorf("failed to insert notification: %w", err) 49 + } 50 + 51 + return nil 52 + } 53 + 54 + func GetNotificationsByDid(e Execer, did string, limit, offset int) ([]Notification, error) { 55 + query := ` 56 + select 57 + id, 58 + recipient_did, 59 + actor_did, 60 + subject_uri, 61 + state, 62 + type, 63 + created_at 64 + from 65 + notifications 66 + where 67 + recipient_did = ? 68 + order by created_at desc 69 + limit ? offset ? 70 + ` 71 + 72 + rows, err := e.Query(query, did, limit, offset) 73 + if err != nil { 74 + return nil, fmt.Errorf("failed to query notifications: %w", err) 75 + } 76 + defer rows.Close() 77 + 78 + var notifications []Notification 79 + for rows.Next() { 80 + var notification Notification 81 + var createdAtStr string 82 + var subjectUriStr string 83 + 84 + err := rows.Scan( 85 + &notification.ID, 86 + &notification.RecipientDid, 87 + &notification.ActorDid, 88 + &subjectUriStr, 89 + &notification.State, 90 + &notification.Type, 91 + &createdAtStr, 92 + ) 93 + if err != nil { 94 + return nil, fmt.Errorf("failed to scan notification row: %w", err) 95 + } 96 + 97 + createdAt, err := time.Parse(time.RFC3339, createdAtStr) 98 + if err != nil { 99 + return nil, fmt.Errorf("failed to parse createdAt string '%s': %w", createdAtStr, err) 100 + } 101 + notification.CreatedAt = createdAt 102 + 103 + subjectUri, err := syntax.ParseATURI(subjectUriStr) 104 + if err != nil { 105 + return nil, fmt.Errorf("failed to parse at-uri: %w", err) 106 + } 107 + notification.SubjectRkey = subjectUri.RecordKey().String() 108 + 109 + notifications = append(notifications, notification) 110 + } 111 + if err = rows.Err(); err != nil { 112 + return nil, err 113 + } 114 + 115 + return notifications, nil 116 + } 117 + 118 + func GetUnreadNotificationCount(e Execer, recipientDid string) (int, error) { 119 + query := `select count(*) from notifications where recipient_did = ? and state = 'unread';` 120 + 121 + var count int 122 + row := e.QueryRow(query, recipientDid) 123 + if err := row.Scan(&count); err != nil { 124 + return 0, fmt.Errorf("failed to get unread notification count: %w", err) 125 + } 126 + 127 + return count, nil 128 + } 129 + 130 + func MarkAllNotificationsAsRead(e Execer, did string) error { 131 + query := ` 132 + update notifications 133 + set state = 'read' 134 + where recipient_did = ? and state = 'unread'; 135 + ` 136 + 137 + _, err := e.Exec(query, did) 138 + if err != nil { 139 + return fmt.Errorf("failed to mark notifications as read for did %s: %w", did, err) 140 + } 141 + 142 + return nil 143 + }