This repository has no description
0

Configure Feed

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

core / appview / db / focus.go
5.6 kB 216 lines
1package db 2 3import ( 4 "database/sql" 5 "errors" 6 "fmt" 7 "strings" 8 "time" 9 10 "github.com/bluesky-social/indigo/atproto/syntax" 11 "tangled.org/core/appview/models" 12) 13 14// notification types that qualify for focus mode 15var FocusEligibleTypes = []models.NotificationType{ 16 models.NotificationTypeIssueCreated, 17 models.NotificationTypeIssueReopen, 18 models.NotificationTypeIssueCommented, 19 models.NotificationTypePullCreated, 20 models.NotificationTypePullReopen, 21 models.NotificationTypePullCommented, 22 models.NotificationTypeUserMentioned, 23} 24 25func focusEligiblePlaceholders() (string, []any) { 26 placeholders := make([]string, len(FocusEligibleTypes)) 27 args := make([]any, len(FocusEligibleTypes)) 28 for i, t := range FocusEligibleTypes { 29 placeholders[i] = "?" 30 args[i] = string(t) 31 } 32 return strings.Join(placeholders, ", "), args 33} 34 35// marks a user as currently focusing 36func BeginFocus(e Execer, did string) error { 37 _, err := e.Exec(`insert or replace into focusing (did) values (?)`, did) 38 if err != nil { 39 return fmt.Errorf("BeginFocus: %w", err) 40 } 41 return nil 42} 43 44// remove the focusing flag for a user 45func EndFocus(e Execer, did string) error { 46 _, err := e.Exec(`delete from focusing where did = ?`, did) 47 if err != nil { 48 return fmt.Errorf("EndFocus: %w", err) 49 } 50 return nil 51} 52 53// whether a user is currently in focus mode 54func GetFocusStatus(e Execer, did string) (bool, error) { 55 var exists bool 56 err := e.QueryRow(`select exists(select 1 from focusing where did = ?)`, did).Scan(&exists) 57 if errors.Is(err, sql.ErrNoRows) { 58 return false, nil 59 } 60 if err != nil { 61 return false, fmt.Errorf("GetFocusStatus: %w", err) 62 } 63 return exists, nil 64} 65 66// oldest unread focus-eligible notification for the user, with its related entity populated 67// 68// returns nil, nil when empty 69func GetNextFocusItem(e Execer, did string) (*models.NotificationWithEntity, error) { 70 placeholders, typeArgs := focusEligiblePlaceholders() 71 72 query := fmt.Sprintf(` 73 select 74 n.id, n.recipient_did, n.actor_did, n.type, n.entity_type, n.entity_id, 75 n.read, n.created, n.repo_id, n.issue_id, n.pull_id, 76 r.id as r_id, r.did as r_did, r.rkey as r_rkey, r.name as r_name, r.description as r_description, r.website as r_website, r.topics as r_topics, 77 i.id as i_id, i.did as i_did, i.issue_id as i_issue_id, i.title as i_title, i.open as i_open, 78 p.id as p_id, p.owner_did as p_owner_did, p.pull_id as p_pull_id, p.title as p_title, p.state as p_state 79 from notifications n 80 left join repos r on n.repo_id = r.id 81 left join issues i on n.issue_id = i.id 82 left join pulls p on n.pull_id = p.id 83 where n.recipient_did = ? 84 and n.read = 0 85 and n.type in (%s) 86 order by n.created asc 87 limit 1 88 `, placeholders) 89 90 args := append([]any{did}, typeArgs...) 91 92 row := e.QueryRow(query, args...) 93 94 var n models.Notification 95 var typeStr string 96 var createdStr string 97 var repo models.Repo 98 var issue models.Issue 99 var pull models.Pull 100 var rId, iId, pId sql.NullInt64 101 var rDid, rRkey, rName, rDescription, rWebsite, rTopicStr sql.NullString 102 var iDid sql.NullString 103 var iIssueId sql.NullInt64 104 var iTitle sql.NullString 105 var iOpen sql.NullBool 106 var pOwnerDid sql.NullString 107 var pPullId sql.NullInt64 108 var pTitle sql.NullString 109 var pState sql.NullInt64 110 111 err := row.Scan( 112 &n.ID, &n.RecipientDid, &n.ActorDid, &typeStr, &n.EntityType, &n.EntityId, 113 &n.Read, &createdStr, &n.RepoId, &n.IssueId, &n.PullId, 114 &rId, &rDid, &rRkey, &rName, &rDescription, &rWebsite, &rTopicStr, 115 &iId, &iDid, &iIssueId, &iTitle, &iOpen, 116 &pId, &pOwnerDid, &pPullId, &pTitle, &pState, 117 ) 118 if errors.Is(err, sql.ErrNoRows) { 119 return nil, nil 120 } 121 if err != nil { 122 return nil, fmt.Errorf("GetNextFocusItem: %w", err) 123 } 124 125 n.Type = models.NotificationType(typeStr) 126 n.Created, err = time.Parse(time.RFC3339, createdStr) 127 if err != nil { 128 return nil, fmt.Errorf("GetNextFocusItem: parse created: %w", err) 129 } 130 131 entry := &models.NotificationWithEntity{Notification: &n} 132 133 if rId.Valid { 134 repo.Id = rId.Int64 135 if rDid.Valid { 136 repo.Did = rDid.String 137 } 138 if rRkey.Valid { 139 repo.Rkey = rRkey.String 140 } 141 if rName.Valid { 142 repo.Name = rName.String 143 } 144 if rDescription.Valid { 145 repo.Description = rDescription.String 146 } 147 if rWebsite.Valid { 148 repo.Website = rWebsite.String 149 } 150 if rTopicStr.Valid { 151 repo.Topics = strings.Fields(rTopicStr.String) 152 } 153 entry.Repo = &repo 154 } 155 156 if iId.Valid { 157 issue.Id = iId.Int64 158 if iDid.Valid { 159 issue.Did = iDid.String 160 } 161 if iIssueId.Valid { 162 issue.IssueId = int(iIssueId.Int64) 163 } 164 if iTitle.Valid { 165 issue.Title = iTitle.String 166 } 167 if iOpen.Valid { 168 issue.Open = iOpen.Bool 169 } 170 entry.Issue = &issue 171 } 172 173 if pId.Valid { 174 pull.ID = pId.Int64 175 if pOwnerDid.Valid { 176 pull.OwnerDid = syntax.DID(pOwnerDid.String) 177 } 178 if pPullId.Valid { 179 pull.PullId = pPullId.Int64 180 } 181 if pTitle.Valid { 182 pull.Title = pTitle.String 183 } 184 if pState.Valid { 185 pull.State = models.PullState(pState.Int64) 186 } 187 entry.Pull = &pull 188 } 189 190 return entry, nil 191} 192 193// returns the number of unread focus-eligible notifications for a user (not sure if we need this?) 194func CountFocusNotifs(e Execer, did string) (int64, error) { 195 placeholders, typeArgs := focusEligiblePlaceholders() 196 197 query := fmt.Sprintf(` 198 select count(1) 199 from notifications 200 where recipient_did = ? 201 and read = 0 202 and type in (%s) 203 `, placeholders) 204 205 args := append([]any{did}, typeArgs...) 206 207 var count int64 208 err := e.QueryRow(query, args...).Scan(&count) 209 if errors.Is(err, sql.ErrNoRows) { 210 return 0, nil 211 } 212 if err != nil { 213 return 0, fmt.Errorf("CountFocusNotifs: %w", err) 214 } 215 return count, nil 216}