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

feat: add comment notifications

brookjeynes.dev 9a8736ce 3a45af20

verified
+43 -7
+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)
+2 -2
internal/server/views/friends.templ
··· 11 <div class="container mx-auto max-w-2xl px-4 py-8"> 12 <div class="flex items-center justify-between mb-8"> 13 <div> 14 - <h1 class="text-3xl font-bold text-gray-900">Friends</h1> 15 - <p class="text-gray-600 mt-1">Connect with fellow language learners</p> 16 </div> 17 </div> 18 <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
··· 11 <div class="container mx-auto max-w-2xl px-4 py-8"> 12 <div class="flex items-center justify-between mb-8"> 13 <div> 14 + <h1 class="text-3xl font-bold">Friends</h1> 15 + <p class="mt-1">Connect with fellow language learners</p> 16 </div> 17 </div> 18 <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
+17 -2
internal/server/views/partials/notification.templ
··· 13 <h1 class="font-semibold">New Follower</h1> 14 <p class="text-sm mt-1"> 15 <a class="hover:underline" href={ templ.SafeURL("/" + params.Notification.ActorDid) }> 16 - { params.Notification.ActorDid } 17 </a> started following you 18 </p> 19 </div> ··· 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:
··· 13 <h1 class="font-semibold">New Follower</h1> 14 <p class="text-sm mt-1"> 15 <a class="hover:underline" href={ templ.SafeURL("/" + params.Notification.ActorDid) }> 16 + &commat;{ params.Notification.ActorBskyHandle } 17 </a> started following you 18 </p> 19 </div> ··· 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: