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

feat: add comment notifications

brookjeynes.dev 9a8736ce 3a45af20

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