feat: add email notification for post like
This commit is contained in:
@@ -18,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prabhupadasays/spsays-bff/internal/model"
|
||||
"github.com/prabhupadasays/spsays-bff/internal/repository"
|
||||
@@ -83,6 +84,16 @@ func (s *NotificationService) MarkAllRead(ctx context.Context, userID string) er
|
||||
return s.notifs.MarkAllRead(ctx, userID)
|
||||
}
|
||||
|
||||
// LikeEmailPref / SetLikeEmailPref back the Settings → Notifications toggle
|
||||
// for aggregated like emails.
|
||||
func (s *NotificationService) LikeEmailPref(ctx context.Context, userID string) (bool, error) {
|
||||
return s.notifs.LikeEmailsEnabled(ctx, userID)
|
||||
}
|
||||
|
||||
func (s *NotificationService) SetLikeEmailPref(ctx context.Context, userID string, enabled bool) error {
|
||||
return s.notifs.SetLikeEmailsEnabled(ctx, userID, enabled)
|
||||
}
|
||||
|
||||
// NotifyFollowRequest tells `recipientID` that `actorID` asked to follow
|
||||
// them. Persists the row and emails the recipient (if they have an email
|
||||
// on file).
|
||||
@@ -107,13 +118,13 @@ func (s *NotificationService) NotifyPostLike(ctx context.Context, postID, author
|
||||
if authorID == "" || likerID == "" || authorID == likerID {
|
||||
return
|
||||
}
|
||||
// Aggregate into the single (author, post) like-notification row, getting
|
||||
// back how many likers it now represents.
|
||||
count, err := s.notifs.UpsertLike(ctx, authorID, likerID, postID)
|
||||
// Aggregate into the single (author, post) like-notification row. Returns
|
||||
// the new liker count and whether an aggregated email is due (throttled).
|
||||
count, emailDue, err := s.notifs.UpsertLike(ctx, authorID, likerID, postID, likeEmailThrottle)
|
||||
if err != nil {
|
||||
s.logger.Warn("aggregate like notification failed",
|
||||
zap.String("recipient", authorID), zap.Error(err))
|
||||
count = 1
|
||||
count, emailDue = 1, false
|
||||
}
|
||||
|
||||
actorUser, _ := s.users.FindByID(ctx, likerID)
|
||||
@@ -136,24 +147,51 @@ func (s *NotificationService) NotifyPostLike(ctx context.Context, postID, author
|
||||
}
|
||||
}
|
||||
|
||||
// Web Push — tapping opens the author's own feed anchored to the post.
|
||||
// Tagged per-post so repeated likes collapse into one notification.
|
||||
if s.push == nil {
|
||||
return
|
||||
}
|
||||
// Tapping a notification opens the author's own feed anchored to the post.
|
||||
author, _ := s.users.FindByID(ctx, authorID)
|
||||
url := s.frontendURL + "/notifications"
|
||||
if author, err := s.users.FindByID(ctx, authorID); err == nil && author != nil {
|
||||
if author != nil {
|
||||
url = s.frontendURL + "/u/" + author.Handle + "/posts#post-" + postID
|
||||
}
|
||||
s.push.SendToUser(ctx, authorID, PushPayload{
|
||||
Title: "New Like",
|
||||
Body: body,
|
||||
URL: url,
|
||||
Icon: s.iconURL(),
|
||||
Tag: NotifPostLike + ":" + postID,
|
||||
})
|
||||
|
||||
// Web Push — tagged per-post so repeated likes collapse into one.
|
||||
if s.push != nil {
|
||||
s.push.SendToUser(ctx, authorID, PushPayload{
|
||||
Title: "New Like",
|
||||
Body: body,
|
||||
URL: url,
|
||||
Icon: s.iconURL(),
|
||||
Tag: NotifPostLike + ":" + postID,
|
||||
})
|
||||
}
|
||||
|
||||
// Aggregated email — only when the throttle says one is due (so a burst of
|
||||
// likes yields a single email), the author has an address, and they
|
||||
// haven't opted out. `body` already aggregates ("X and N others …").
|
||||
if !emailDue || author == nil || author.Email == nil || strings.TrimSpace(*author.Email) == "" {
|
||||
return
|
||||
}
|
||||
if enabled, perr := s.notifs.LikeEmailsEnabled(ctx, authorID); perr != nil || !enabled {
|
||||
return
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := likeEmailTpl.Execute(&buf, map[string]any{
|
||||
"RecipientName": author.Name,
|
||||
"Message": body,
|
||||
"URL": url,
|
||||
}); err != nil {
|
||||
s.logger.Warn("render like email failed", zap.Error(err))
|
||||
return
|
||||
}
|
||||
if err := s.mail.Send(strings.TrimSpace(*author.Email), "New likes on your post — Prabhupāda Says", buf.String()); err != nil {
|
||||
s.logger.Warn("like email send failed", zap.String("to", *author.Email), zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// likeEmailThrottle is the minimum gap between aggregated like emails for a
|
||||
// single post — a burst of likes within the window produces one email.
|
||||
const likeEmailThrottle = 6 * time.Hour
|
||||
|
||||
// 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 {
|
||||
@@ -402,3 +440,20 @@ var reminderEmailTpl = template.Must(template.New("reminder").Parse(`<div style=
|
||||
<p style="margin-top:1em">Your servants,<br>
|
||||
<strong>The Prabhupāda Says team</strong></p>
|
||||
</div>`))
|
||||
|
||||
// Aggregated like email. Message is "X and N others liked your post"; the CTA
|
||||
// opens the author's post.
|
||||
var likeEmailTpl = template.Must(template.New("postLike").Parse(`<div style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;color:#2a2118;max-width:560px;margin:0 auto;font-size:17px;line-height:1.7">
|
||||
<p style="font-size:18px;color:#9a6b2f;letter-spacing:0.04em;margin-bottom:1em">Hare Kṛṣṇa{{if .RecipientName}}, {{.RecipientName}}{{end}} 🙏</p>
|
||||
|
||||
<p>{{.Message}}.</p>
|
||||
|
||||
<p style="margin:1.25em 0">
|
||||
<a href="{{.URL}}" style="display:inline-block;background:#9a6b2f;color:#fff;text-decoration:none;padding:0.6em 1.3em;border-radius:8px;font-weight:600">View your post</a>
|
||||
</p>
|
||||
|
||||
<p style="margin-top:1.5em;font-size:14px;color:#7a6a55">You can turn off like emails any time in Settings → Notifications.</p>
|
||||
|
||||
<p style="margin-top:1em">Your servants,<br>
|
||||
<strong>The Prabhupāda Says team</strong></p>
|
||||
</div>`))
|
||||
|
||||
Reference in New Issue
Block a user