Yōten: A social platform for tracking the essential points of your language learning yoten.app
0

Configure Feed

Select the types of activity you want to include in your feed.

yoten / internal / db / notification.go
3.6 kB 142 lines
1package db 2 3import ( 4 "database/sql" 5 "fmt" 6 "time" 7 8 "github.com/bluesky-social/indigo/atproto/syntax" 9 "yoten.app/internal/domain" 10) 11 12func CreateNotification(e Execer, recipientDID, actorDID, subjectURI string, notificationType domain.NotificationType) error { 13 query := ` 14 insert into notifications 15 (recipient_did, actor_did, subject_uri, type) 16 values (?, ?, ?, ?) 17 ` 18 19 _, err := e.Exec(query, recipientDID, actorDID, subjectURI, notificationType) 20 if err != nil { 21 return fmt.Errorf("failed to insert notification: %w", err) 22 } 23 24 return nil 25} 26 27func Notifications(e Execer, did string, limit, offset int) ([]domain.Notification, error) { 28 // for mention-type notifications the subject_uri is the comment at-uri. 29 // we left-join comments so we can resolve the study session uri needed for 30 // building the anchor link in the ui. 31 // TODO: we should simplify this modal. perhaps we can use unions to handle 32 // the different notification types? 33 34 query := ` 35 select 36 n.id, 37 n.recipient_did, 38 n.actor_did, 39 n.subject_uri, 40 n.state, 41 n.type, 42 n.created_at, 43 c.study_session_uri 44 from 45 notifications n 46 left join comments c 47 on n.type = 'mention' 48 and ('at://' || c.did || '/app.yoten.feed.comment/' || c.rkey) = n.subject_uri 49 where 50 n.recipient_did = ? 51 order by n.created_at desc 52 limit ? offset ? 53 ` 54 55 rows, err := e.Query(query, did, limit, offset) 56 if err != nil { 57 return nil, fmt.Errorf("failed to query notifications: %w", err) 58 } 59 defer rows.Close() 60 61 var notifications []domain.Notification 62 for rows.Next() { 63 var notification domain.Notification 64 var createdAtStr string 65 var subjectURIStr string 66 var studySessionURIStr sql.NullString 67 68 err := rows.Scan( 69 &notification.ID, 70 &notification.RecipientDID, 71 &notification.ActorDID, 72 &subjectURIStr, 73 &notification.State, 74 &notification.Type, 75 &createdAtStr, 76 &studySessionURIStr, 77 ) 78 if err != nil { 79 return nil, fmt.Errorf("failed to scan notification row: %w", err) 80 } 81 82 createdAt, err := time.Parse(time.RFC3339, createdAtStr) 83 if err != nil { 84 return nil, fmt.Errorf("failed to parse createdAt string '%s': %w", createdAtStr, err) 85 } 86 notification.CreatedAt = createdAt 87 88 subjectURI, err := syntax.ParseATURI(subjectURIStr) 89 if err != nil { 90 return nil, fmt.Errorf("failed to parse at-uri: %w", err) 91 } 92 notification.SubjectRKey = subjectURI.RecordKey().String() 93 94 subjectDID, err := subjectURI.Authority().AsDID() 95 if err != nil { 96 return nil, fmt.Errorf("failed to identify subject did: %w", err) 97 } 98 notification.SubjectDID = subjectDID.String() 99 100 if studySessionURIStr.Valid { 101 ssURI, err := syntax.ParseATURI(studySessionURIStr.String) 102 if err != nil { 103 return nil, fmt.Errorf("failed to parse study session at-uri: %w", err) 104 } 105 notification.StudySessionURI = &ssURI 106 } 107 108 notifications = append(notifications, notification) 109 } 110 if err = rows.Err(); err != nil { 111 return nil, err 112 } 113 114 return notifications, nil 115} 116 117func UnreadNotificationCount(e Execer, recipientDID string) (int, error) { 118 query := `select count(*) from notifications where recipient_did = ? and state = 0;` 119 120 var count int 121 row := e.QueryRow(query, recipientDID) 122 if err := row.Scan(&count); err != nil { 123 return 0, fmt.Errorf("failed to get unread notification count: %w", err) 124 } 125 126 return count, nil 127} 128 129func MarkNotificationsAsRead(e Execer, did string) error { 130 query := ` 131 update notifications 132 set state = 1 133 where recipient_did = ? and state = 0; 134 ` 135 136 _, err := e.Exec(query, did) 137 if err != nil { 138 return fmt.Errorf("failed to mark notifications as read for did %s: %w", did, err) 139 } 140 141 return nil 142}