feat: add feedback acknowledgement

This commit is contained in:
Krispire Admin
2026-06-04 01:55:39 +05:30
parent e9a96f1ff9
commit d9678ebb3b
7 changed files with 176 additions and 31 deletions
+101 -23
View File
@@ -97,6 +97,52 @@ func (s *FeedbackService) Submit(ctx context.Context, userID *string, category,
// rather than an error — the end state the admin wanted is already true.
var ErrAlreadyResolved = errors.New("feedback already resolved")
// ErrAlreadyAcknowledged is the acknowledge-path counterpart. Same
// benign-redirect treatment in the handler.
var ErrAlreadyAcknowledged = errors.New("feedback already acknowledged")
// Acknowledge marks a feedback row acknowledged (one-way) and, if a
// recipient address is known, emails the author that the team has seen
// their feedback and — if it needs work — the devs have started, with a
// second email promised on completion.
//
// Mirrors Resolve: MarkAcknowledged's `acknowledged_at IS NULL` guard
// guarantees the email fires at most once; the email is fire-and-forget.
func (s *FeedbackService) Acknowledge(ctx context.Context, id string) error {
acked, err := s.repo.MarkAcknowledged(ctx, id)
if err != nil {
return err
}
if !acked {
return ErrAlreadyAcknowledged
}
f, err := s.repo.FindByID(ctx, id)
if err != nil {
s.logger.Warn("feedback acknowledged but hydrate failed; skipping email",
zap.String("feedback_id", id), zap.Error(err))
return nil
}
to := feedbackReplyTo(f)
if to == "" {
s.logger.Info("feedback acknowledged; no recipient address, skipping email",
zap.String("feedback_id", id))
return nil
}
body, err := renderFeedbackAcknowledgedEmail(f)
if err != nil {
s.logger.Warn("render feedback-acknowledged email failed", zap.Error(err))
return nil
}
if err := s.mail.Send(to, "We've received your feedback — Prabhupāda Says", body); err != nil {
s.logger.Warn("feedback-acknowledged email send failed",
zap.String("to", to), zap.Error(err))
}
return nil
}
// Resolve marks a feedback row resolved (one-way) and, if a recipient
// address is known, sends the author a Vaishnava-styled thank-you note.
//
@@ -231,36 +277,67 @@ func derefOr(p *string, fallback string) string {
}
// feedbackResolvedEmailTpl is the author-facing thank-you sent when an
// admin marks their feedback resolved. Vaishnava-styled — warm,
// devotional register, addressed to the submitter by name when known.
var feedbackResolvedEmailTpl = template.Must(template.New("feedbackResolved").Parse(`<div style="font-family:Georgia,'Times New Roman',serif;color:#2a2118;max-width:560px;margin:0 auto;line-height:1.7">
<p style="font-size:15px;color:#9a6b2f;letter-spacing:0.04em;margin-bottom:1.25em">Hare Kṛṣṇa{{if .Name}}, {{.Name}}{{end}} 🙏</p>
// admin marks their feedback resolved. Vaishnava-styled, short, and
// quotes back the exact feedback the devotee submitted so they know
// precisely what was addressed. Base font is 17px for comfortable
// reading on phones.
var feedbackResolvedEmailTpl = template.Must(template.New("feedbackResolved").Parse(`<div style="font-family:Georgia,'Times New Roman',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 .Name}}, {{.Name}}{{end}} 🙏</p>
<p>Please accept our humble obeisances. All glories to Śrīla Prabhupāda.</p>
<p>The feedback you shared with us has been addressed and the necessary
changes are now live:</p>
<p>We are writing to let you know that the feedback you kindly shared with
us has been <strong>acknowledged and addressed</strong>. The necessary
features and fixes you pointed us toward have now been implemented.</p>
<blockquote style="margin:1em 0;padding:0.75em 1em;border-left:3px solid #d6b27a;background:#faf5ea;color:#5a4a32;font-style:italic;white-space:pre-wrap">{{.Message}}</blockquote>
<p>Your observations help us serve Śrīla Prabhupāda's <em>vāṇī</em> more
faithfully, and we are sincerely grateful that you took the time to write.
Devotee feedback like yours is one of the ways this humble service keeps
improving.</p>
<p>We warmly invite you to continue using
<p>Thank you for helping us serve Śrīla Prabhupāda's <em>vāṇī</em> more
faithfully. Please continue using
<a href="https://prabhupadasays.org" style="color:#9a6b2f;font-weight:600">prabhupadasays.org</a>
with even greater enthusiasm — there is always more of Śrīla Prabhupāda's
nectar waiting to be relished.</p>
with even greater enthusiasm.</p>
<p style="margin-top:1.75em">Your servants,<br>
<p style="margin-top:1.5em">Your servants,<br>
<strong>The Prabhupāda Says team</strong></p>
<hr style="border:none;border-top:1px solid #e8ddc9;margin:2em 0">
<p style="font-size:12px;color:#a89878;font-style:italic">
“Books are the basis.” — His Divine Grace A.C. Bhaktivedanta Swami Prabhupāda
</p>
</div>`))
// feedbackAcknowledgedEmailTpl is the author-facing "we've seen it"
// note sent when an admin presses "Acknowledge". Same Vaishnava styling
// and 17px base as the resolved email, quotes the submission back, and
// promises a follow-up email on completion.
var feedbackAcknowledgedEmailTpl = template.Must(template.New("feedbackAcknowledged").Parse(`<div style="font-family:Georgia,'Times New Roman',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 .Name}}, {{.Name}}{{end}} 🙏</p>
<p>Thank you for the feedback you shared with us:</p>
<blockquote style="margin:1em 0;padding:0.75em 1em;border-left:3px solid #d6b27a;background:#faf5ea;color:#5a4a32;font-style:italic;white-space:pre-wrap">{{.Message}}</blockquote>
<p>We acknowledge your feedback and have noted it carefully. If it calls
for a new feature or a fix, our dev team has already begun working on it.
You'll receive another email from us once it has been implemented or
resolved.</p>
<p>Thank you for helping us serve Śrīla Prabhupāda's <em>vāṇī</em> more
faithfully. Please continue using
<a href="https://prabhupadasays.org" style="color:#9a6b2f;font-weight:600">prabhupadasays.org</a>
with even greater enthusiasm.</p>
<p style="margin-top:1.5em">Your servants,<br>
<strong>The Prabhupāda Says team</strong></p>
</div>`))
func renderFeedbackAcknowledgedEmail(f *model.Feedback) (string, error) {
name := ""
if f.AuthorName != nil {
name = strings.TrimSpace(*f.AuthorName)
}
var buf bytes.Buffer
if err := feedbackAcknowledgedEmailTpl.Execute(&buf, map[string]any{
"Name": name,
"Message": strings.TrimSpace(f.Message),
}); err != nil {
return "", err
}
return buf.String(), nil
}
func renderFeedbackResolvedEmail(f *model.Feedback) (string, error) {
// Prefer the account display name; fall back to no name (the
// template drops the comma cleanly when Name is empty).
@@ -270,7 +347,8 @@ func renderFeedbackResolvedEmail(f *model.Feedback) (string, error) {
}
var buf bytes.Buffer
if err := feedbackResolvedEmailTpl.Execute(&buf, map[string]any{
"Name": name,
"Name": name,
"Message": strings.TrimSpace(f.Message),
}); err != nil {
return "", err
}