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

feat: add comment notifications

brookjeynes.dev 01fe5574 01643ce6

verified
+40 -4
+11 -1
internal/consumer/ingester.go
··· 582 if err != nil { 583 return fmt.Errorf("failed to parse study session at-uri: %w", err) 584 } 585 586 body := record.Body 587 - if len(body) == 0 { 588 return fmt.Errorf("invalid body: length cannot be 0") 589 } 590 ··· 619 tx.Rollback() 620 return fmt.Errorf("failed to upsert comment record: %w", err) 621 } 622 return tx.Commit() 623 case models.CommitOperationDelete: 624 log.Println("deleting comment from pds request")
··· 582 if err != nil { 583 return fmt.Errorf("failed to parse study session at-uri: %w", err) 584 } 585 + subjectDid, err := subjectUri.Authority().AsDID() 586 + if err != nil { 587 + return fmt.Errorf("failed to identify subject did: %w", err) 588 + } 589 590 body := record.Body 591 + if len(strings.TrimSpace(body)) == 0 { 592 return fmt.Errorf("invalid body: length cannot be 0") 593 } 594 ··· 623 tx.Rollback() 624 return fmt.Errorf("failed to upsert comment record: %w", err) 625 } 626 + 627 + err = db.CreateNotification(tx, subjectDid.String(), did, subjectUri.String(), db.NotificationTypeComment) 628 + if err != nil { 629 + log.Println("failed to create notification record:", err) 630 + } 631 + 632 return tx.Commit() 633 case models.CommitOperationDelete: 634 log.Println("deleting comment from pds request")
+1 -1
internal/db/db.go
··· 193 subject_uri text not null, 194 195 state text not null default 'unread' check(state in ('unread', 'read')), 196 - type text not null check(type in ('follow', 'reaction')), 197 198 created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 199
··· 193 subject_uri text not null, 194 195 state text not null default 'unread' check(state in ('unread', 'read')), 196 + type text not null check(type in ('follow', 'reaction', 'comment')), 197 198 created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), 199
+7
internal/db/notification.go
··· 12 const ( 13 NotificationTypeFollow NotificationType = "follow" 14 NotificationTypeReaction NotificationType = "reaction" 15 ) 16 17 type NotificationState string ··· 31 RecipientDid string 32 ActorDid string 33 SubjectRkey string 34 State NotificationState 35 Type NotificationType 36 CreatedAt time.Time ··· 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 }
··· 12 const ( 13 NotificationTypeFollow NotificationType = "follow" 14 NotificationTypeReaction NotificationType = "reaction" 15 + NotificationTypeComment NotificationType = "comment" 16 ) 17 18 type NotificationState string ··· 32 RecipientDid string 33 ActorDid string 34 SubjectRkey string 35 + SubjectDid string 36 State NotificationState 37 Type NotificationType 38 CreatedAt time.Time ··· 107 return nil, fmt.Errorf("failed to parse at-uri: %w", err) 108 } 109 notification.SubjectRkey = subjectUri.RecordKey().String() 110 + subjectDid, err := subjectUri.Authority().AsDID() 111 + if err != nil { 112 + return nil, fmt.Errorf("failed to identify subject did: %w", err) 113 + } 114 + notification.SubjectDid = subjectDid.String() 115 116 notifications = append(notifications, notification) 117 }
+1 -1
internal/server/handlers/comment.go
··· 95 if !h.Config.Core.Dev { 96 event := posthog.Capture{ 97 DistinctId: user.Did, 98 - Event: ph.CommentRecordDeletedEvent, 99 Properties: posthog.NewProperties(). 100 Set("is_reply", newComment.ParentCommentUri != nil). 101 Set("character_count", len(newComment.Body)).
··· 95 if !h.Config.Core.Dev { 96 event := posthog.Capture{ 97 DistinctId: user.Did, 98 + Event: ph.CommentRecordCreatedEvent, 99 Properties: posthog.NewProperties(). 100 Set("is_reply", newComment.ParentCommentUri != nil). 101 Set("character_count", len(newComment.Body)).
+4
internal/server/handlers/study-session.go
··· 728 return 729 } 730 731 nextPage := 0 732 if len(commentFeed) > pageSize { 733 nextPage = int(page + 1)
··· 728 return 729 } 730 731 + commentFeed = utils.Filter(commentFeed, func(cwlp db.CommentWithLocalProfile) bool { 732 + return !cwlp.IsDeleted 733 + }) 734 + 735 nextPage := 0 736 if len(commentFeed) > pageSize { 737 nextPage = int(page + 1)
+16 -1
internal/server/views/partials/notification.templ
··· 23 <p class="text-sm mt-1"> 24 <a class="hover:underline" href={ templ.SafeURL("/" + params.Notification.ActorDid) }> 25 &commat;{ params.Notification.ActorBskyHandle } 26 - </a> reacted to your study session 27 </p> 28 </div> 29 default:
··· 23 <p class="text-sm mt-1"> 24 <a class="hover:underline" href={ templ.SafeURL("/" + params.Notification.ActorDid) }> 25 &commat;{ params.Notification.ActorBskyHandle } 26 + </a> reacted to your 27 + <a class="hover:underline" href={ templ.SafeURL("/" + params.Notification.SubjectDid + "/session/" + params.Notification.SubjectRkey) }> 28 + study session 29 + </a> 30 + </p> 31 + </div> 32 + case db.NotificationTypeComment: 33 + <div> 34 + <h1 class="font-semibold">New Comment</h1> 35 + <p class="text-sm mt-1"> 36 + <a class="hover:underline" href={ templ.SafeURL("/" + params.Notification.ActorDid) }> 37 + &commat;{ params.Notification.ActorBskyHandle } 38 + </a> commented on your 39 + <a class="hover:underline" href={ templ.SafeURL("/" + params.Notification.SubjectDid + "/session/" + params.Notification.SubjectRkey) }> 40 + study session 41 + </a> 42 </p> 43 </div> 44 default: