Monorepo for Tangled tangled.org
2

Configure Feed

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

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