feat: add inline mentions in comments
This commit is contained in:
@@ -4,12 +4,41 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/prabhupadasays/spsays-bff/internal/model"
|
||||
"github.com/prabhupadasays/spsays-bff/internal/repository"
|
||||
)
|
||||
|
||||
// mentionHandleRe matches @handle tokens. The leading boundary (line start or
|
||||
// a non-handle character that isn't itself "@") keeps it from firing inside
|
||||
// emails (foo@bar) or "@@".
|
||||
var mentionHandleRe = regexp.MustCompile(`(^|[^A-Za-z0-9_@])@([A-Za-z0-9_]{1,30})`)
|
||||
|
||||
// mentionHandles pulls unique @handles out of a comment body (capped, so a
|
||||
// pathological body can't fan out unboundedly).
|
||||
func mentionHandles(body string) []string {
|
||||
matches := mentionHandleRe.FindAllStringSubmatch(body, -1)
|
||||
if matches == nil {
|
||||
return nil
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
out := make([]string, 0, len(matches))
|
||||
for _, m := range matches {
|
||||
key := strings.ToLower(m[2])
|
||||
if seen[key] {
|
||||
continue
|
||||
}
|
||||
seen[key] = true
|
||||
out = append(out, m[2])
|
||||
if len(out) >= 20 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// maxCommentLen caps a comment/reply body.
|
||||
const maxCommentLen = 2000
|
||||
|
||||
@@ -87,6 +116,14 @@ func (s *CommentService) Add(ctx context.Context, viewerID, postID string, paren
|
||||
go s.notif.NotifyPostComment(context.Background(), postID, notifyUserID, viewerID)
|
||||
}
|
||||
}
|
||||
|
||||
// Ping anyone @mentioned in the body — skipping the actor and whoever was
|
||||
// already notified above (post/parent author) so they aren't double-pinged.
|
||||
if s.notif != nil {
|
||||
if handles := mentionHandles(body); len(handles) > 0 {
|
||||
go s.notif.NotifyCommentMentions(context.Background(), postID, viewerID, handles, notifyUserID)
|
||||
}
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ const (
|
||||
NotifPostMention = "post_mention"
|
||||
NotifPostComment = "post_comment"
|
||||
NotifCommentReply = "comment_reply"
|
||||
NotifCommentMention = "comment_mention"
|
||||
|
||||
// Reminder kinds are delivered live (email + push + a websocket toast)
|
||||
// but NOT persisted — they're ephemeral nudges, not history.
|
||||
@@ -394,8 +395,11 @@ func (s *NotificationService) notifyComment(ctx context.Context, kind, postID, r
|
||||
url = s.frontendURL + "/u/" + h + "/posts#post-" + postID
|
||||
}
|
||||
title, body := "New comment", who+" commented on your post"
|
||||
if kind == NotifCommentReply {
|
||||
switch kind {
|
||||
case NotifCommentReply:
|
||||
title, body = "New reply", who+" replied to your comment"
|
||||
case NotifCommentMention:
|
||||
title, body = "You were mentioned", who+" mentioned you in a comment"
|
||||
}
|
||||
s.push.SendToUser(ctx, recipientID, PushPayload{
|
||||
Title: title,
|
||||
@@ -406,6 +410,31 @@ func (s *NotificationService) notifyComment(ctx context.Context, kind, postID, r
|
||||
})
|
||||
}
|
||||
|
||||
// NotifyCommentMentions tells each user named by `handles` that `actorID`
|
||||
// mentioned them in a comment on `postID`. Handles are resolved to users,
|
||||
// deduped, and the actor plus any `skip` ids (e.g. the post/parent author who
|
||||
// already got a comment/reply notification) are excluded so nobody is
|
||||
// double-pinged. Reuses the comment notification path (row + realtime + push).
|
||||
func (s *NotificationService) NotifyCommentMentions(ctx context.Context, postID, actorID string, handles []string, skip ...string) {
|
||||
if actorID == "" || len(handles) == 0 {
|
||||
return
|
||||
}
|
||||
skipSet := map[string]bool{actorID: true}
|
||||
for _, id := range skip {
|
||||
if id != "" {
|
||||
skipSet[id] = true
|
||||
}
|
||||
}
|
||||
for _, h := range handles {
|
||||
u, err := s.users.FindByHandle(ctx, h)
|
||||
if err != nil || u == nil || skipSet[u.ID] {
|
||||
continue
|
||||
}
|
||||
skipSet[u.ID] = true // dedupe repeats of the same mention
|
||||
s.notifyComment(ctx, NotifCommentMention, postID, u.ID, actorID)
|
||||
}
|
||||
}
|
||||
|
||||
// likeMessage renders the aggregated like text: "X liked your post" or
|
||||
// "X and N others liked your post".
|
||||
func likeMessage(who string, count int) string {
|
||||
|
||||
Reference in New Issue
Block a user