feat(notification): add email notification for invited to answer

This commit is contained in:
LinkinStars 2023-05-24 10:35:38 +08:00
parent 8b55c05522
commit e12e0094d2
7 changed files with 129 additions and 11 deletions

View File

@ -170,7 +170,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo)
questionActivityRepo := activity.NewQuestionActivityRepo(dataData, activityRepo, userRankRepo)
answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, questionActivityRepo)
questionService := service.NewQuestionService(questionRepo, tagCommonService, questionCommon, userCommon, revisionService, metaService, collectionCommon, answerActivityService, dataData)
questionService := service.NewQuestionService(questionRepo, tagCommonService, questionCommon, userCommon, userRepo, revisionService, metaService, collectionCommon, answerActivityService, dataData, emailService)
answerService := service.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService, userRoleRelService)
questionController := controller.NewQuestionController(questionService, answerService, rankService)
dashboardService := dashboard.NewDashboardService(questionRepo, answerRepo, commentCommonRepo, voteRepo, userRepo, reportRepo, configRepo, siteInfoCommonService, serviceConf, dataData)

View File

@ -401,6 +401,11 @@ backend:
other: "[{{.SiteName}}] {{.DisplayName}} answered your question"
body:
other: "<strong><a href='{{.AnswerUrl}}'>{{.QuestionTitle}}</a></strong><br><br>\n\n<small>{{.DisplayName}}:</small><br>\n<blockquote>{{.AnswerSummary}}</blockquote><br>\n<a href='{{.AnswerUrl}}'>View it on {{.SiteName}}</a><br><br>\n\n<small>You are receiving this because you authored the thread. <a href='{{.UnsubscribeUrl}}'>Unsubscribe</a></small>"
invited_you_to_answer:
title:
other: "[{{.SiteName}}] {{.DisplayName}} invited you to answer"
body:
other: "<strong><a href='{{.InviteUrl}}'>{{.QuestionTitle}}</a></strong><br><br>\n\n<small>{{.DisplayName}}:</small><br>\n<blockquote>I think you may know the answer.</blockquote><br>\n<a href='{{.InviteUrl}}'>View it on {{.SiteName}}</a><br><br>\n\n<small>You are receiving this because you authored the thread. <a href='{{.UnsubscribeUrl}}'>Unsubscribe</a></small>"
new_comment:
title:
other: "[{{.SiteName}}] {{.DisplayName}} commented on your post"

View File

@ -378,6 +378,8 @@ backend:
other: 踩了答案
up_voted_comment:
other: 赞了评论
invited_you_to_answer:
other: 邀请你回答问题
email_tpl:
change_email:
title:
@ -388,12 +390,17 @@ backend:
title:
other: "[{{.SiteName}}] {{.DisplayName}} 回答了您的问题"
body:
other: "<strong><a href='{{.AnswerUrl}}'>{{.QuestionTitle}}</a></strong><br><br>\n\n<small>{{.DisplayName}}:</small><br>\n<blockquote>{{.AnswerSummary}}</blockquote><br>\n<a href='{{.AnswerUrl}}'>在 {{.SiteName}} 上查看</a><br><br>\n\n<small>您会收到此邮件是因为您是该讨论的作者。<a href='{{.UnsubscribeUrl}}'>取消订阅</a></small>"
other: "<strong><a href='{{.AnswerUrl}}'>{{.QuestionTitle}}</a></strong><br><br>\n\n<small>{{.DisplayName}}:</small><br>\n<blockquote>{{.AnswerSummary}}</blockquote><br>\n<a href='{{.AnswerUrl}}'>在 {{.SiteName}} 上查看</a><br><br>\n\n<small>您会收到此邮件是因为您开启了订阅。<a href='{{.UnsubscribeUrl}}'>取消订阅</a></small>"
invited_you_to_answer:
title:
other: "[{{.SiteName}}] {{.DisplayName}} 邀请您回答问题"
body:
other: "<strong><a href='{{.InviteUrl}}'>{{.QuestionTitle}}</a></strong><br><br>\n\n<small>{{.DisplayName}}:</small><br>\n<blockquote>我想你可能知道答案。</blockquote><br>\n<a href='{{.InviteUrl}}'>在 {{.SiteName}} 上查看</a><br><br>\n\n<small>您会收到此邮件是因为您开启了订阅. <a href='{{.UnsubscribeUrl}}'>取消订阅</a></small>"
new_comment:
title:
other: "[{{.SiteName}}] {{.DisplayName}} 评论了您的帖子"
body:
other: "<strong><a href='{{.CommentUrl}}'>{{.QuestionTitle}}</a></strong><br><br>\n\n<small>{{.DisplayName}}:</small><br>\n<blockquote>{{.CommentSummary}}</blockquote><br>\n<a href='{{.CommentUrl}}'>在 {{.SiteName}} 上查看</a><br><br>\n\n<small>您会收到此邮件是因为您是该讨论的作者。<a href='{{.UnsubscribeUrl}}'>取消订阅</a></small>"
other: "<strong><a href='{{.CommentUrl}}'>{{.QuestionTitle}}</a></strong><br><br>\n\n<small>{{.DisplayName}}:</small><br>\n<blockquote>{{.CommentSummary}}</blockquote><br>\n<a href='{{.CommentUrl}}'>在 {{.SiteName}} 上查看</a><br><br>\n\n<small>您会收到此邮件是因为您开启了订阅。<a href='{{.UnsubscribeUrl}}'>取消订阅</a></small>"
pass_reset:
title:
other: "[{{.SiteName }}] 重置密码"

View File

@ -18,4 +18,7 @@ const (
EmailTplKeyTestTitle = "email_tpl.test.title"
EmailTplKeyTestBody = "email_tpl.test.body"
EmailTplKeyInvitedAnswerTitle = "email_tpl.invited_you_to_answer.title"
EmailTplKeyInvitedAnswerBody = "email_tpl.invited_you_to_answer.body"
)

View File

@ -47,6 +47,21 @@ type NewAnswerTemplateData struct {
UnsubscribeUrl string
}
type NewInviteAnswerTemplateRawData struct {
InviterDisplayName string
QuestionTitle string
QuestionID string
UnsubscribeCode string
}
type NewInviteAnswerTemplateData struct {
SiteName string
DisplayName string
QuestionTitle string
InviteUrl string
UnsubscribeUrl string
}
type NewCommentTemplateRawData struct {
CommentUserDisplayName string
QuestionTitle string

View File

@ -242,7 +242,6 @@ func (es *EmailService) NewAnswerTemplate(ctx context.Context, raw *schema.NewAn
AnswerSummary: raw.AnswerSummary,
UnsubscribeUrl: fmt.Sprintf("%s/users/unsubscribe?code=%s", siteInfo.SiteUrl, raw.UnsubscribeCode),
}
templateData.SiteName = siteInfo.Name
lang := handler.GetLangByCtx(ctx)
title = translator.TrWithData(lang, constant.EmailTplKeyNewAnswerTitle, templateData)
@ -250,6 +249,27 @@ func (es *EmailService) NewAnswerTemplate(ctx context.Context, raw *schema.NewAn
return title, body, nil
}
// NewInviteAnswerTemplate new invite answer template
func (es *EmailService) NewInviteAnswerTemplate(ctx context.Context, raw *schema.NewInviteAnswerTemplateRawData) (
title, body string, err error) {
siteInfo, err := es.GetSiteGeneral(ctx)
if err != nil {
return
}
templateData := &schema.NewInviteAnswerTemplateData{
SiteName: siteInfo.Name,
DisplayName: raw.InviterDisplayName,
QuestionTitle: raw.QuestionTitle,
InviteUrl: fmt.Sprintf("%s/questions/%s", siteInfo.SiteUrl, raw.QuestionID),
UnsubscribeUrl: fmt.Sprintf("%s/users/unsubscribe?code=%s", siteInfo.SiteUrl, raw.UnsubscribeCode),
}
lang := handler.GetLangByCtx(ctx)
title = translator.TrWithData(lang, constant.EmailTplKeyInvitedAnswerTitle, templateData)
body = translator.TrWithData(lang, constant.EmailTplKeyInvitedAnswerBody, templateData)
return title, body, nil
}
// NewCommentTemplate new comment template
func (es *EmailService) NewCommentTemplate(ctx context.Context, raw *schema.NewCommentTemplateRawData) (
title, body string, err error) {
@ -271,7 +291,6 @@ func (es *EmailService) NewCommentTemplate(ctx context.Context, raw *schema.NewC
templateData.CommentUrl = fmt.Sprintf("%s/questions/%s?commentId=%s", siteInfo.SiteUrl,
raw.QuestionID, raw.CommentID)
}
templateData.SiteName = siteInfo.Name
lang := handler.GetLangByCtx(ctx)
title = translator.TrWithData(lang, constant.EmailTplKeyNewCommentTitle, templateData)

View File

@ -19,6 +19,7 @@ import (
"github.com/answerdev/answer/internal/service/activity"
"github.com/answerdev/answer/internal/service/activity_queue"
collectioncommon "github.com/answerdev/answer/internal/service/collection_common"
"github.com/answerdev/answer/internal/service/export"
"github.com/answerdev/answer/internal/service/meta"
"github.com/answerdev/answer/internal/service/notice_queue"
"github.com/answerdev/answer/internal/service/permission"
@ -26,10 +27,12 @@ import (
"github.com/answerdev/answer/internal/service/revision_common"
tagcommon "github.com/answerdev/answer/internal/service/tag_common"
usercommon "github.com/answerdev/answer/internal/service/user_common"
"github.com/answerdev/answer/pkg/encryption"
"github.com/answerdev/answer/pkg/htmltext"
"github.com/answerdev/answer/pkg/uid"
"github.com/jinzhu/copier"
"github.com/segmentfault/pacman/errors"
"github.com/segmentfault/pacman/i18n"
"github.com/segmentfault/pacman/log"
"golang.org/x/net/context"
)
@ -42,11 +45,13 @@ type QuestionService struct {
tagCommon *tagcommon.TagCommonService
questioncommon *questioncommon.QuestionCommon
userCommon *usercommon.UserCommon
userRepo usercommon.UserRepo
revisionService *revision_common.RevisionService
metaService *meta.MetaService
collectionCommon *collectioncommon.CollectionCommon
answerActivityService *activity.AnswerActivityService
data *data.Data
emailService *export.EmailService
}
func NewQuestionService(
@ -54,23 +59,26 @@ func NewQuestionService(
tagCommon *tagcommon.TagCommonService,
questioncommon *questioncommon.QuestionCommon,
userCommon *usercommon.UserCommon,
userRepo usercommon.UserRepo,
revisionService *revision_common.RevisionService,
metaService *meta.MetaService,
collectionCommon *collectioncommon.CollectionCommon,
answerActivityService *activity.AnswerActivityService,
data *data.Data,
emailService *export.EmailService,
) *QuestionService {
return &QuestionService{
questionRepo: questionRepo,
tagCommon: tagCommon,
questioncommon: questioncommon,
userCommon: userCommon,
userRepo: userRepo,
revisionService: revisionService,
metaService: metaService,
collectionCommon: collectionCommon,
answerActivityService: answerActivityService,
data: data,
emailService: emailService,
}
}
@ -536,20 +544,28 @@ func (qs *QuestionService) UpdateQuestionCheckTags(ctx context.Context, req *sch
}
func (qs *QuestionService) UpdateQuestionInviteUser(ctx context.Context, req *schema.QuestionUpdateInviteUser) (err error) {
originQuestion, exist, err := qs.questionRepo.GetQuestion(ctx, req.ID)
if err != nil {
return err
}
if !exist {
return errors.NotFound(reason.ObjectNotFound)
}
//verify invite user
inviteUserInfoList, err := qs.userCommon.BatchGetUserBasicInfoByUserNames(ctx, req.InviteUser)
if err != nil {
log.Error("BatchGetUserBasicInfoByUserNames error", err.Error())
}
inviteUser := make([]string, 0)
inviteUserIDs := make([]string, 0)
for _, item := range req.InviteUser {
_, ok := inviteUserInfoList[item]
if ok {
inviteUser = append(inviteUser, inviteUserInfoList[item].ID)
inviteUserIDs = append(inviteUserIDs, inviteUserInfoList[item].ID)
}
}
inviteUserStr := ""
inviteUserByte, err := json.Marshal(inviteUser)
inviteUserByte, err := json.Marshal(inviteUserIDs)
if err != nil {
log.Error("json.Marshal error", err.Error())
inviteUserStr = "[]"
@ -564,12 +580,31 @@ func (qs *QuestionService) UpdateQuestionInviteUser(ctx context.Context, req *sc
if saveerr != nil {
return saveerr
}
qs.notificationInviteUser(ctx, inviteUser, req.ID, req.UserID)
go qs.notificationInviteUser(ctx, inviteUserIDs, originQuestion.ID, originQuestion.Title, req.UserID)
return nil
}
func (qs *QuestionService) notificationInviteUser(
ctx context.Context, invitedUserIDs []string, questionID, questionUserID string) {
ctx context.Context, invitedUserIDs []string, questionID, questionTitle, questionUserID string) {
inviter, exist, err := qs.userCommon.GetUserBasicInfoByID(ctx, questionUserID)
if err != nil {
log.Error(err)
return
}
if !exist {
log.Warnf("user %s not found", questionUserID)
return
}
users, err := qs.userRepo.BatchGetByID(ctx, invitedUserIDs)
if err != nil {
log.Error(err)
return
}
invitee := make(map[string]*entity.User, len(users))
for _, user := range users {
invitee[user.ID] = user
}
for _, userID := range invitedUserIDs {
msg := &schema.NotificationMsg{
ReceiverUserID: userID,
@ -580,6 +615,40 @@ func (qs *QuestionService) notificationInviteUser(
msg.ObjectType = constant.QuestionObjectType
msg.NotificationAction = constant.NotificationInvitedYouToAnswer
notice_queue.AddNotification(msg)
userInfo, ok := invitee[userID]
if !ok {
log.Warnf("user %s not found", userID)
return
}
if userInfo.NoticeStatus == schema.NoticeStatusOff || len(userInfo.EMail) == 0 {
return
}
rawData := &schema.NewInviteAnswerTemplateRawData{
InviterDisplayName: inviter.DisplayName,
QuestionTitle: questionTitle,
QuestionID: questionID,
UnsubscribeCode: encryption.MD5(userInfo.Pass),
}
codeContent := &schema.EmailCodeContent{
SourceType: schema.UnsubscribeSourceType,
Email: userInfo.EMail,
UserID: userInfo.ID,
}
// If receiver has set language, use it to send email.
if len(userInfo.Language) > 0 {
ctx = context.WithValue(ctx, constant.AcceptLanguageFlag, i18n.Language(userInfo.Language))
}
title, body, err := qs.emailService.NewInviteAnswerTemplate(ctx, rawData)
if err != nil {
log.Error(err)
return
}
go qs.emailService.SendAndSaveCodeWithTime(
ctx, userInfo.EMail, title, body, rawData.UnsubscribeCode, codeContent.ToJSONString(), 7*24*time.Hour)
}
}