feat: add feedback acknowledgement
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user