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

feat: show comment count on study session

Signed-off-by: brookjeynes <me@brookjeynes.dev>

brookjeynes.dev 08cab6d1 6b3b4fd6

verified
+110 -11
+48
internal/db/comment.go
··· 9 9 "github.com/bluesky-social/indigo/atproto/syntax" 10 10 "yoten.app/api/yoten" 11 11 "yoten.app/internal/types" 12 + "yoten.app/internal/utils" 12 13 ) 13 14 14 15 type CommentFeedItem struct { ··· 133 134 } 134 135 135 136 return comment, nil 137 + } 138 + 139 + func GetCommentCountForSession(e Execer, studySessionUri string) (int64, error) { 140 + query := `select count(*) from comments where study_session_uri = ? and is_deleted == 0` 141 + 142 + var totalComments int64 143 + 144 + err := e.QueryRow(query, studySessionUri).Scan(&totalComments) 145 + if err != nil { 146 + return 0, fmt.Errorf("failed to query total comments: %w", err) 147 + } 148 + 149 + return totalComments, nil 150 + } 151 + 152 + func GetCommentCountsForSessions(e Execer, uris []string) (map[string]int64, error) { 153 + if len(uris) == 0 { 154 + return map[string]int64{}, nil 155 + } 156 + 157 + placeholders := GetPlaceholders(len(uris)) 158 + 159 + query := fmt.Sprintf( 160 + `select study_session_uri, count(*) as total_comments 161 + from comments 162 + where study_session_uri in (%s) and is_deleted == 0 163 + group by study_session_uri`, placeholders) 164 + 165 + args := utils.Map(uris, func(uri string) any { 166 + return any(uri) 167 + }) 168 + rows, err := e.Query(query, args...) 169 + if err != nil { 170 + return nil, err 171 + } 172 + defer rows.Close() 173 + 174 + result := make(map[string]int64) 175 + for rows.Next() { 176 + var uri string 177 + var count int64 178 + if err := rows.Scan(&uri, &count); err != nil { 179 + return nil, err 180 + } 181 + result[uri] = count 182 + } 183 + return result, nil 136 184 } 137 185 138 186 func GetCommentsForSession(e Execer, studySessionUri string, limit, offset int) ([]CommentWithLocalProfile, error) {
+60 -10
internal/db/study-session.go
··· 12 12 13 13 "yoten.app/api/yoten" 14 14 "yoten.app/internal/types" 15 + "yoten.app/internal/utils" 15 16 ) 16 17 17 18 var ( ··· 28 29 Did string 29 30 Rkey string 30 31 // 256 characters 31 - Description string 32 - Activity Activity 33 - Resource *Resource 34 - Language Language 35 - XpGained int 36 - Duration time.Duration 37 - Date time.Time 38 - Reactions []ReactionEvent 39 - CreatedAt time.Time 32 + Description string 33 + Activity Activity 34 + Resource *Resource 35 + Language Language 36 + XpGained int 37 + Duration time.Duration 38 + Date time.Time 39 + Reactions []ReactionEvent 40 + CommentCount int64 41 + CreatedAt time.Time 40 42 } 41 43 42 44 func (ss StudySession) GetRkey() string { ··· 216 218 217 219 studySessions = append(studySessions, &session) 218 220 } 219 - 220 221 if err = rows.Err(); err != nil { 221 222 return nil, fmt.Errorf("failed to iterate study session rows: %w", err) 222 223 } 223 224 225 + uris := utils.Map(studySessions, func(session *StudySession) string { 226 + return string(session.StudySessionAt()) 227 + }) 228 + commentCounts, err := GetCommentCountsForSessions(e, uris) 229 + if err != nil { 230 + log.Println("failed to get comment count:", err) 231 + } 232 + for _, item := range studySessions { 233 + if count, ok := commentCounts[string(item.StudySessionAt())]; ok { 234 + item.CommentCount = count 235 + } else { 236 + item.CommentCount = 0 237 + } 238 + } 239 + 224 240 return studySessions, nil 225 241 } 226 242 ··· 302 318 log.Println("failed to retrieve reactions", err) 303 319 } 304 320 session.Reactions = reactions 321 + 322 + commentCount, err := GetCommentCountForSession(e, string(session.StudySessionAt())) 323 + if err != nil { 324 + log.Println("failed to retrieve comment count", err) 325 + } 326 + session.CommentCount = commentCount 305 327 306 328 return &session, nil 307 329 } ··· 537 559 return rows.Err() 538 560 } 539 561 562 + func populateCommentsForFeed(e Execer, feedItems []*StudySessionFeedItem) error { 563 + if len(feedItems) == 0 { 564 + return nil 565 + } 566 + 567 + uris := utils.Map(feedItems, func(item *StudySessionFeedItem) string { 568 + return item.StudySessionAt().String() 569 + }) 570 + commentCounts, err := GetCommentCountsForSessions(e, uris) 571 + if err != nil { 572 + return fmt.Errorf("failed to get comment counts: %w", err) 573 + } 574 + 575 + for _, item := range feedItems { 576 + if count, ok := commentCounts[string(item.StudySessionAt())]; ok { 577 + item.CommentCount = count 578 + } else { 579 + item.CommentCount = 0 580 + } 581 + } 582 + 583 + return nil 584 + } 585 + 540 586 func getFeed(e Execer, query string, args ...any) ([]*StudySessionFeedItem, error) { 541 587 rows, err := e.Query(query, args...) 542 588 if err != nil { ··· 566 612 } 567 613 568 614 if err := populateResourcesForFeed(e, feedItems); err != nil { 615 + return nil, err 616 + } 617 + 618 + if err := populateCommentsForFeed(e, feedItems); err != nil { 569 619 return nil, err 570 620 } 571 621
+2 -1
internal/server/views/partials/study-session.templ
··· 156 156 SessionRkey: params.StudySession.Rkey, 157 157 ReactionEvents: params.StudySession.Reactions, 158 158 }) 159 - <a href={ studySessionUrl } title="comments"> 159 + <a class="flex items-center gap-1" href={ studySessionUrl } title="comments"> 160 160 <i class="w-5 h-5" data-lucide="message-square-share"></i> 161 + <span>{ params.StudySession.CommentCount }</span> 161 162 </a> 162 163 </div> 163 164 <div class="flex flex-col sm:flex-row sm:items-center gap-2 text-sm text-text-muted">