mirror of https://gitee.com/answerdev/answer.git
feat(notification): Email notice when a user answers or comment
This commit is contained in:
parent
27e84f9ea8
commit
81f914d72f
|
@ -135,7 +135,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||||
tagCommonService := tag_common2.NewTagCommonService(tagCommonRepo, tagRelRepo, tagRepo, revisionService, siteInfoCommonService)
|
tagCommonService := tag_common2.NewTagCommonService(tagCommonRepo, tagRelRepo, tagRepo, revisionService, siteInfoCommonService)
|
||||||
objService := object_info.NewObjService(answerRepo, questionRepo, commentCommonRepo, tagCommonRepo, tagCommonService)
|
objService := object_info.NewObjService(answerRepo, questionRepo, commentCommonRepo, tagCommonRepo, tagCommonService)
|
||||||
voteRepo := activity_common.NewVoteRepo(dataData, activityRepo)
|
voteRepo := activity_common.NewVoteRepo(dataData, activityRepo)
|
||||||
commentService := comment2.NewCommentService(commentRepo, commentCommonRepo, userCommon, objService, voteRepo)
|
commentService := comment2.NewCommentService(commentRepo, commentCommonRepo, userCommon, objService, voteRepo, emailService, userRepo)
|
||||||
rolePowerRelRepo := role.NewRolePowerRelRepo(dataData)
|
rolePowerRelRepo := role.NewRolePowerRelRepo(dataData)
|
||||||
rolePowerRelService := role2.NewRolePowerRelService(rolePowerRelRepo, userRoleRelService)
|
rolePowerRelService := role2.NewRolePowerRelService(rolePowerRelRepo, userRoleRelService)
|
||||||
rankService := rank2.NewRankService(userCommon, userRankRepo, objService, userRoleRelService, rolePowerRelService, configRepo)
|
rankService := rank2.NewRankService(userCommon, userRankRepo, objService, userRoleRelService, rolePowerRelService, configRepo)
|
||||||
|
@ -166,7 +166,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||||
answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, questionActivityRepo)
|
answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, questionActivityRepo)
|
||||||
questionService := service.NewQuestionService(questionRepo, tagCommonService, questionCommon, userCommon, revisionService, metaService, collectionCommon, answerActivityService, dataData)
|
questionService := service.NewQuestionService(questionRepo, tagCommonService, questionCommon, userCommon, revisionService, metaService, collectionCommon, answerActivityService, dataData)
|
||||||
questionController := controller.NewQuestionController(questionService, rankService)
|
questionController := controller.NewQuestionController(questionService, rankService)
|
||||||
answerService := service.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo)
|
answerService := service.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService)
|
||||||
dashboardService := dashboard.NewDashboardService(questionRepo, answerRepo, commentCommonRepo, voteRepo, userRepo, reportRepo, configRepo, siteInfoCommonService, serviceConf, dataData)
|
dashboardService := dashboard.NewDashboardService(questionRepo, answerRepo, commentCommonRepo, voteRepo, userRepo, reportRepo, configRepo, siteInfoCommonService, serviceConf, dataData)
|
||||||
answerController := controller.NewAnswerController(answerService, rankService, dashboardService)
|
answerController := controller.NewAnswerController(answerService, rankService, dashboardService)
|
||||||
searchParser := search_parser.NewSearchParser(tagCommonService, userCommon)
|
searchParser := search_parser.NewSearchParser(tagCommonService, userCommon)
|
||||||
|
|
|
@ -152,7 +152,7 @@ func (tc *TemplateController) QuestionList(ctx *gin.Context) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc *TemplateController) QuestionInfo301Jump(ctx *gin.Context, siteInfo *schema.TemplateSiteInfoResp, correctTitle bool) (jump bool, url string) {
|
func (tc *TemplateController) QuestionInfoeRdirect(ctx *gin.Context, siteInfo *schema.TemplateSiteInfoResp, correctTitle bool) (jump bool, url string) {
|
||||||
id := ctx.Param("id")
|
id := ctx.Param("id")
|
||||||
title := ctx.Param("title")
|
title := ctx.Param("title")
|
||||||
titleIsAnswerID := false
|
titleIsAnswerID := false
|
||||||
|
@ -182,6 +182,9 @@ func (tc *TemplateController) QuestionInfo301Jump(ctx *gin.Context, siteInfo *sc
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
url = fmt.Sprintf("%s/%s", url, htmltext.UrlTitle(detail.Title))
|
url = fmt.Sprintf("%s/%s", url, htmltext.UrlTitle(detail.Title))
|
||||||
|
if titleIsAnswerID {
|
||||||
|
url = fmt.Sprintf("%s/%s", url, title)
|
||||||
|
}
|
||||||
return true, url
|
return true, url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -217,9 +220,9 @@ func (tc *TemplateController) QuestionInfo(ctx *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
siteInfo := tc.SiteInfo(ctx)
|
siteInfo := tc.SiteInfo(ctx)
|
||||||
jump, jumpurl := tc.QuestionInfo301Jump(ctx, siteInfo, correctTitle)
|
jump, jumpurl := tc.QuestionInfoeRdirect(ctx, siteInfo, correctTitle)
|
||||||
if jump {
|
if jump {
|
||||||
ctx.Redirect(http.StatusMovedPermanently, jumpurl)
|
ctx.Redirect(http.StatusFound, jumpurl)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -535,3 +535,28 @@ func (uc *UserController) UserRanking(ctx *gin.Context) {
|
||||||
resp, err := uc.userService.UserRanking(ctx)
|
resp, err := uc.userService.UserRanking(ctx)
|
||||||
handler.HandleResponse(ctx, err, resp)
|
handler.HandleResponse(ctx, err, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserUnsubscribeEmailNotification unsubscribe email notification
|
||||||
|
// @Summary unsubscribe email notification
|
||||||
|
// @Description unsubscribe email notification
|
||||||
|
// @Tags User
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} handler.RespBody{}
|
||||||
|
// @Router /answer/api/v1/user/email/notification [put]
|
||||||
|
func (uc *UserController) UserUnsubscribeEmailNotification(ctx *gin.Context) {
|
||||||
|
req := &schema.UserUnsubscribeEmailNotificationReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Content = uc.emailService.VerifyUrlExpired(ctx, req.Code)
|
||||||
|
if len(req.Content) == 0 {
|
||||||
|
handler.HandleResponse(ctx, errors.Forbidden(reason.EmailVerifyURLExpired),
|
||||||
|
&schema.ForbiddenResp{Type: schema.ForbiddenReasonTypeURLExpired})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := uc.userService.UserUnsubscribeEmailNotification(ctx, req)
|
||||||
|
handler.HandleResponse(ctx, err, nil)
|
||||||
|
}
|
||||||
|
|
|
@ -23,8 +23,8 @@ func NewEmailRepo(data *data.Data) export.EmailRepo {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCode The email code is used to verify that the link in the message is out of date
|
// SetCode The email code is used to verify that the link in the message is out of date
|
||||||
func (e *emailRepo) SetCode(ctx context.Context, code, content string) error {
|
func (e *emailRepo) SetCode(ctx context.Context, code, content string, duration time.Duration) error {
|
||||||
err := e.data.Cache.SetString(ctx, code, content, 10*time.Minute)
|
err := e.data.Cache.SetString(ctx, code, content, duration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,6 +106,7 @@ func (a *AnswerAPIRouter) RegisterMustUnAuthAnswerAPIRouter(r *gin.RouterGroup)
|
||||||
r.POST("/user/password/reset", a.userController.RetrievePassWord)
|
r.POST("/user/password/reset", a.userController.RetrievePassWord)
|
||||||
r.POST("/user/password/replacement", a.userController.UseRePassWord)
|
r.POST("/user/password/replacement", a.userController.UseRePassWord)
|
||||||
r.GET("/user/info", a.userController.GetUserInfoByUserID)
|
r.GET("/user/info", a.userController.GetUserInfoByUserID)
|
||||||
|
r.PUT("/user/email/notification", a.userController.UserUnsubscribeEmailNotification)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) {
|
func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) {
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
package schema
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
const (
|
||||||
|
AccountActivationSourceType SourceType = "account-activation"
|
||||||
|
PasswordResetSourceType SourceType = "password-reset"
|
||||||
|
ConfirmNewEmailSourceType SourceType = "password-reset"
|
||||||
|
UnsubscribeSourceType SourceType = "unsubscribe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SourceType string
|
||||||
|
|
||||||
|
type EmailCodeContent struct {
|
||||||
|
SourceType SourceType `json:"source_type"`
|
||||||
|
Email string `json:"e_mail"`
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *EmailCodeContent) ToJSONString() string {
|
||||||
|
codeBytes, _ := json.Marshal(r)
|
||||||
|
return string(codeBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *EmailCodeContent) FromJSONString(data string) error {
|
||||||
|
return json.Unmarshal([]byte(data), &r)
|
||||||
|
}
|
||||||
|
|
||||||
|
type NewAnswerTemplateRawData struct {
|
||||||
|
AnswerUserDisplayName string
|
||||||
|
QuestionTitle string
|
||||||
|
QuestionID string
|
||||||
|
AnswerID string
|
||||||
|
AnswerSummary string
|
||||||
|
UnsubscribeCode string
|
||||||
|
}
|
||||||
|
|
||||||
|
type NewAnswerTemplateData struct {
|
||||||
|
SiteName string
|
||||||
|
DisplayName string
|
||||||
|
QuestionTitle string
|
||||||
|
AnswerUrl string
|
||||||
|
AnswerSummary string
|
||||||
|
UnsubscribeUrl string
|
||||||
|
}
|
||||||
|
|
||||||
|
type NewCommentTemplateRawData struct {
|
||||||
|
CommentUserDisplayName string
|
||||||
|
QuestionTitle string
|
||||||
|
QuestionID string
|
||||||
|
AnswerID string
|
||||||
|
CommentID string
|
||||||
|
CommentSummary string
|
||||||
|
UnsubscribeCode string
|
||||||
|
}
|
||||||
|
|
||||||
|
type NewCommentTemplateData struct {
|
||||||
|
SiteName string
|
||||||
|
DisplayName string
|
||||||
|
QuestionTitle string
|
||||||
|
CommentUrl string
|
||||||
|
CommentSummary string
|
||||||
|
UnsubscribeUrl string
|
||||||
|
}
|
|
@ -349,8 +349,8 @@ func (u *UserRePassWordRequest) Check() (errFields []*validator.FormErrorField,
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserNoticeSetRequest struct {
|
type UserNoticeSetRequest struct {
|
||||||
UserID string `json:"-" ` // user_id
|
NoticeSwitch bool `json:"notice_switch"`
|
||||||
NoticeSwitch bool `json:"notice_switch" `
|
UserID string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserNoticeSetResp struct {
|
type UserNoticeSetResp struct {
|
||||||
|
@ -396,20 +396,6 @@ type UserChangeEmailSendCodeReq struct {
|
||||||
UserID string `json:"-"`
|
UserID string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmailCodeContent struct {
|
|
||||||
Email string `json:"e_mail"`
|
|
||||||
UserID string `json:"user_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EmailCodeContent) ToJSONString() string {
|
|
||||||
codeBytes, _ := json.Marshal(r)
|
|
||||||
return string(codeBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EmailCodeContent) FromJSONString(data string) error {
|
|
||||||
return json.Unmarshal([]byte(data), &r)
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserChangeEmailVerifyReq struct {
|
type UserChangeEmailVerifyReq struct {
|
||||||
Code string `validate:"required,gt=0,lte=500" json:"code"`
|
Code string `validate:"required,gt=0,lte=500" json:"code"`
|
||||||
Content string `json:"-"`
|
Content string `json:"-"`
|
||||||
|
@ -440,3 +426,9 @@ type UserRankingSimpleInfo struct {
|
||||||
// avatar
|
// avatar
|
||||||
Avatar string `json:"avatar"`
|
Avatar string `json:"avatar"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserUnsubscribeEmailNotificationReq user unsubscribe email notification request
|
||||||
|
type UserUnsubscribeEmailNotificationReq struct {
|
||||||
|
Code string `validate:"required,gt=0,lte=500" json:"code"`
|
||||||
|
Content string `json:"-"`
|
||||||
|
}
|
||||||
|
|
|
@ -15,11 +15,13 @@ import (
|
||||||
"github.com/answerdev/answer/internal/service/activity_queue"
|
"github.com/answerdev/answer/internal/service/activity_queue"
|
||||||
answercommon "github.com/answerdev/answer/internal/service/answer_common"
|
answercommon "github.com/answerdev/answer/internal/service/answer_common"
|
||||||
collectioncommon "github.com/answerdev/answer/internal/service/collection_common"
|
collectioncommon "github.com/answerdev/answer/internal/service/collection_common"
|
||||||
|
"github.com/answerdev/answer/internal/service/export"
|
||||||
"github.com/answerdev/answer/internal/service/notice_queue"
|
"github.com/answerdev/answer/internal/service/notice_queue"
|
||||||
"github.com/answerdev/answer/internal/service/permission"
|
"github.com/answerdev/answer/internal/service/permission"
|
||||||
questioncommon "github.com/answerdev/answer/internal/service/question_common"
|
questioncommon "github.com/answerdev/answer/internal/service/question_common"
|
||||||
"github.com/answerdev/answer/internal/service/revision_common"
|
"github.com/answerdev/answer/internal/service/revision_common"
|
||||||
usercommon "github.com/answerdev/answer/internal/service/user_common"
|
usercommon "github.com/answerdev/answer/internal/service/user_common"
|
||||||
|
"github.com/answerdev/answer/pkg/encryption"
|
||||||
"github.com/segmentfault/pacman/errors"
|
"github.com/segmentfault/pacman/errors"
|
||||||
"github.com/segmentfault/pacman/log"
|
"github.com/segmentfault/pacman/log"
|
||||||
)
|
)
|
||||||
|
@ -36,6 +38,7 @@ type AnswerService struct {
|
||||||
revisionService *revision_common.RevisionService
|
revisionService *revision_common.RevisionService
|
||||||
AnswerCommon *answercommon.AnswerCommon
|
AnswerCommon *answercommon.AnswerCommon
|
||||||
voteRepo activity_common.VoteRepo
|
voteRepo activity_common.VoteRepo
|
||||||
|
emailService *export.EmailService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAnswerService(
|
func NewAnswerService(
|
||||||
|
@ -49,6 +52,7 @@ func NewAnswerService(
|
||||||
answerAcceptActivityRepo *activity.AnswerActivityService,
|
answerAcceptActivityRepo *activity.AnswerActivityService,
|
||||||
answerCommon *answercommon.AnswerCommon,
|
answerCommon *answercommon.AnswerCommon,
|
||||||
voteRepo activity_common.VoteRepo,
|
voteRepo activity_common.VoteRepo,
|
||||||
|
emailService *export.EmailService,
|
||||||
) *AnswerService {
|
) *AnswerService {
|
||||||
return &AnswerService{
|
return &AnswerService{
|
||||||
answerRepo: answerRepo,
|
answerRepo: answerRepo,
|
||||||
|
@ -61,6 +65,7 @@ func NewAnswerService(
|
||||||
answerActivityService: answerAcceptActivityRepo,
|
answerActivityService: answerAcceptActivityRepo,
|
||||||
AnswerCommon: answerCommon,
|
AnswerCommon: answerCommon,
|
||||||
voteRepo: voteRepo,
|
voteRepo: voteRepo,
|
||||||
|
emailService: emailService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +181,8 @@ func (as *AnswerService) Insert(ctx context.Context, req *schema.AnswerAddReq) (
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return insertData.ID, err
|
return insertData.ID, err
|
||||||
}
|
}
|
||||||
as.notificationAnswerTheQuestion(ctx, questionInfo.UserID, insertData.ID, req.UserID)
|
as.notificationAnswerTheQuestion(ctx, questionInfo.UserID, questionInfo.ID, insertData.ID, req.UserID, questionInfo.Title,
|
||||||
|
insertData.OriginalText)
|
||||||
|
|
||||||
activity_queue.AddActivity(&schema.ActivityMsg{
|
activity_queue.AddActivity(&schema.ActivityMsg{
|
||||||
UserID: insertData.UserID,
|
UserID: insertData.UserID,
|
||||||
|
@ -542,7 +548,12 @@ func (as *AnswerService) notificationUpdateAnswer(ctx context.Context, questionU
|
||||||
notice_queue.AddNotification(msg)
|
notice_queue.AddNotification(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as *AnswerService) notificationAnswerTheQuestion(ctx context.Context, questionUserID, answerID, answerUserID string) {
|
func (as *AnswerService) notificationAnswerTheQuestion(ctx context.Context,
|
||||||
|
questionUserID, questionID, answerID, answerUserID, questionTitle, answerSummary string) {
|
||||||
|
// If the question is answered by me, there is no notification for myself.
|
||||||
|
if questionUserID == answerUserID {
|
||||||
|
return
|
||||||
|
}
|
||||||
msg := &schema.NotificationMsg{
|
msg := &schema.NotificationMsg{
|
||||||
TriggerUserID: answerUserID,
|
TriggerUserID: answerUserID,
|
||||||
ReceiverUserID: questionUserID,
|
ReceiverUserID: questionUserID,
|
||||||
|
@ -552,4 +563,43 @@ func (as *AnswerService) notificationAnswerTheQuestion(ctx context.Context, ques
|
||||||
msg.ObjectType = constant.AnswerObjectType
|
msg.ObjectType = constant.AnswerObjectType
|
||||||
msg.NotificationAction = constant.AnswerTheQuestion
|
msg.NotificationAction = constant.AnswerTheQuestion
|
||||||
notice_queue.AddNotification(msg)
|
notice_queue.AddNotification(msg)
|
||||||
|
|
||||||
|
userInfo, exist, err := as.userRepo.GetByUserID(ctx, questionUserID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !exist {
|
||||||
|
log.Warnf("user %s not found", questionUserID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if userInfo.NoticeStatus == schema.NoticeStatusOff || len(userInfo.EMail) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rawData := &schema.NewAnswerTemplateRawData{
|
||||||
|
QuestionTitle: questionTitle,
|
||||||
|
QuestionID: questionID,
|
||||||
|
AnswerID: answerID,
|
||||||
|
AnswerSummary: answerSummary,
|
||||||
|
UnsubscribeCode: encryption.MD5(userInfo.Pass),
|
||||||
|
}
|
||||||
|
answerUser, _, _ := as.userCommon.GetUserBasicInfoByID(ctx, answerUserID)
|
||||||
|
if answerUser != nil {
|
||||||
|
rawData.AnswerUserDisplayName = answerUser.DisplayName
|
||||||
|
}
|
||||||
|
codeContent := &schema.EmailCodeContent{
|
||||||
|
SourceType: schema.UnsubscribeSourceType,
|
||||||
|
Email: userInfo.EMail,
|
||||||
|
UserID: userInfo.ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
title, body, err := as.emailService.NewAnswerTemplate(ctx, rawData)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go as.emailService.SendAndSaveCodeWithTime(
|
||||||
|
ctx, userInfo.EMail, title, body, rawData.UnsubscribeCode, codeContent.ToJSONString(), 7*24*time.Hour)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package comment
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/base/constant"
|
"github.com/answerdev/answer/internal/base/constant"
|
||||||
"github.com/answerdev/answer/internal/base/pager"
|
"github.com/answerdev/answer/internal/base/pager"
|
||||||
|
@ -11,10 +12,12 @@ import (
|
||||||
"github.com/answerdev/answer/internal/service/activity_common"
|
"github.com/answerdev/answer/internal/service/activity_common"
|
||||||
"github.com/answerdev/answer/internal/service/activity_queue"
|
"github.com/answerdev/answer/internal/service/activity_queue"
|
||||||
"github.com/answerdev/answer/internal/service/comment_common"
|
"github.com/answerdev/answer/internal/service/comment_common"
|
||||||
|
"github.com/answerdev/answer/internal/service/export"
|
||||||
"github.com/answerdev/answer/internal/service/notice_queue"
|
"github.com/answerdev/answer/internal/service/notice_queue"
|
||||||
"github.com/answerdev/answer/internal/service/object_info"
|
"github.com/answerdev/answer/internal/service/object_info"
|
||||||
"github.com/answerdev/answer/internal/service/permission"
|
"github.com/answerdev/answer/internal/service/permission"
|
||||||
usercommon "github.com/answerdev/answer/internal/service/user_common"
|
usercommon "github.com/answerdev/answer/internal/service/user_common"
|
||||||
|
"github.com/answerdev/answer/pkg/encryption"
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
"github.com/segmentfault/pacman/errors"
|
"github.com/segmentfault/pacman/errors"
|
||||||
"github.com/segmentfault/pacman/log"
|
"github.com/segmentfault/pacman/log"
|
||||||
|
@ -30,15 +33,6 @@ type CommentRepo interface {
|
||||||
comments []*entity.Comment, total int64, err error)
|
comments []*entity.Comment, total int64, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommentService user service
|
|
||||||
type CommentService struct {
|
|
||||||
commentRepo CommentRepo
|
|
||||||
commentCommonRepo comment_common.CommentCommonRepo
|
|
||||||
userCommon *usercommon.UserCommon
|
|
||||||
voteCommon activity_common.VoteRepo
|
|
||||||
objectInfoService *object_info.ObjService
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommentQuery struct {
|
type CommentQuery struct {
|
||||||
pager.PageCond
|
pager.PageCond
|
||||||
// object id
|
// object id
|
||||||
|
@ -59,19 +53,35 @@ func (c *CommentQuery) GetOrderBy() string {
|
||||||
return "created_at ASC"
|
return "created_at ASC"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CommentService user service
|
||||||
|
type CommentService struct {
|
||||||
|
commentRepo CommentRepo
|
||||||
|
commentCommonRepo comment_common.CommentCommonRepo
|
||||||
|
userCommon *usercommon.UserCommon
|
||||||
|
voteCommon activity_common.VoteRepo
|
||||||
|
objectInfoService *object_info.ObjService
|
||||||
|
emailService *export.EmailService
|
||||||
|
userRepo usercommon.UserRepo
|
||||||
|
}
|
||||||
|
|
||||||
// NewCommentService new comment service
|
// NewCommentService new comment service
|
||||||
func NewCommentService(
|
func NewCommentService(
|
||||||
commentRepo CommentRepo,
|
commentRepo CommentRepo,
|
||||||
commentCommonRepo comment_common.CommentCommonRepo,
|
commentCommonRepo comment_common.CommentCommonRepo,
|
||||||
userCommon *usercommon.UserCommon,
|
userCommon *usercommon.UserCommon,
|
||||||
objectInfoService *object_info.ObjService,
|
objectInfoService *object_info.ObjService,
|
||||||
voteCommon activity_common.VoteRepo) *CommentService {
|
voteCommon activity_common.VoteRepo,
|
||||||
|
emailService *export.EmailService,
|
||||||
|
userRepo usercommon.UserRepo,
|
||||||
|
) *CommentService {
|
||||||
return &CommentService{
|
return &CommentService{
|
||||||
commentRepo: commentRepo,
|
commentRepo: commentRepo,
|
||||||
commentCommonRepo: commentCommonRepo,
|
commentCommonRepo: commentCommonRepo,
|
||||||
userCommon: userCommon,
|
userCommon: userCommon,
|
||||||
voteCommon: voteCommon,
|
voteCommon: voteCommon,
|
||||||
objectInfoService: objectInfoService,
|
objectInfoService: objectInfoService,
|
||||||
|
emailService: emailService,
|
||||||
|
userRepo: userRepo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,9 +122,11 @@ func (cs *CommentService) AddComment(ctx context.Context, req *schema.AddComment
|
||||||
}
|
}
|
||||||
|
|
||||||
if objInfo.ObjectType == constant.QuestionObjectType {
|
if objInfo.ObjectType == constant.QuestionObjectType {
|
||||||
cs.notificationQuestionComment(ctx, objInfo.ObjectCreatorUserID, comment.ID, req.UserID)
|
cs.notificationQuestionComment(ctx, objInfo.ObjectCreatorUserID,
|
||||||
|
objInfo.QuestionID, objInfo.Title, comment.ID, req.UserID, comment.OriginalText)
|
||||||
} else if objInfo.ObjectType == constant.AnswerObjectType {
|
} else if objInfo.ObjectType == constant.AnswerObjectType {
|
||||||
cs.notificationAnswerComment(ctx, objInfo.ObjectCreatorUserID, comment.ID, req.UserID)
|
cs.notificationAnswerComment(ctx, objInfo.QuestionID, objInfo.Title, objInfo.AnswerID,
|
||||||
|
objInfo.ObjectCreatorUserID, comment.ID, req.UserID, comment.OriginalText)
|
||||||
}
|
}
|
||||||
if len(req.MentionUsernameList) > 0 {
|
if len(req.MentionUsernameList) > 0 {
|
||||||
cs.notificationMention(ctx, req.MentionUsernameList, comment.ID, req.UserID)
|
cs.notificationMention(ctx, req.MentionUsernameList, comment.ID, req.UserID)
|
||||||
|
@ -401,7 +413,11 @@ func (cs *CommentService) GetCommentPersonalWithPage(ctx context.Context, req *s
|
||||||
return pager.NewPageModel(total, resp), nil
|
return pager.NewPageModel(total, resp), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *CommentService) notificationQuestionComment(ctx context.Context, questionUserID, commentID, commentUserID string) {
|
func (cs *CommentService) notificationQuestionComment(ctx context.Context, questionUserID,
|
||||||
|
questionID, questionTitle, commentID, commentUserID, commentSummary string) {
|
||||||
|
if questionUserID == commentUserID {
|
||||||
|
return
|
||||||
|
}
|
||||||
msg := &schema.NotificationMsg{
|
msg := &schema.NotificationMsg{
|
||||||
ReceiverUserID: questionUserID,
|
ReceiverUserID: questionUserID,
|
||||||
TriggerUserID: commentUserID,
|
TriggerUserID: commentUserID,
|
||||||
|
@ -411,9 +427,52 @@ func (cs *CommentService) notificationQuestionComment(ctx context.Context, quest
|
||||||
msg.ObjectType = constant.CommentObjectType
|
msg.ObjectType = constant.CommentObjectType
|
||||||
msg.NotificationAction = constant.CommentQuestion
|
msg.NotificationAction = constant.CommentQuestion
|
||||||
notice_queue.AddNotification(msg)
|
notice_queue.AddNotification(msg)
|
||||||
|
|
||||||
|
receiverUserInfo, exist, err := cs.userRepo.GetByUserID(ctx, questionUserID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !exist {
|
||||||
|
log.Warnf("user %s not found", questionUserID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if receiverUserInfo.NoticeStatus == schema.NoticeStatusOff || len(receiverUserInfo.EMail) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rawData := &schema.NewCommentTemplateRawData{
|
||||||
|
QuestionTitle: questionTitle,
|
||||||
|
QuestionID: questionID,
|
||||||
|
CommentID: commentID,
|
||||||
|
CommentSummary: commentSummary,
|
||||||
|
UnsubscribeCode: encryption.MD5(receiverUserInfo.Pass),
|
||||||
|
}
|
||||||
|
commentUser, _, _ := cs.userCommon.GetUserBasicInfoByID(ctx, commentUserID)
|
||||||
|
if commentUser != nil {
|
||||||
|
rawData.CommentUserDisplayName = commentUser.DisplayName
|
||||||
|
}
|
||||||
|
codeContent := &schema.EmailCodeContent{
|
||||||
|
SourceType: schema.UnsubscribeSourceType,
|
||||||
|
Email: receiverUserInfo.EMail,
|
||||||
|
UserID: receiverUserInfo.ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
title, body, err := cs.emailService.NewCommentTemplate(ctx, rawData)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go cs.emailService.SendAndSaveCodeWithTime(
|
||||||
|
ctx, receiverUserInfo.EMail, title, body, rawData.UnsubscribeCode, codeContent.ToJSONString(), 7*24*time.Hour)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *CommentService) notificationAnswerComment(ctx context.Context, answerUserID, commentID, commentUserID string) {
|
func (cs *CommentService) notificationAnswerComment(ctx context.Context,
|
||||||
|
questionID, questionTitle, answerID, answerUserID, commentID, commentUserID, commentSummary string) {
|
||||||
|
if answerUserID == commentUserID {
|
||||||
|
return
|
||||||
|
}
|
||||||
msg := &schema.NotificationMsg{
|
msg := &schema.NotificationMsg{
|
||||||
ReceiverUserID: answerUserID,
|
ReceiverUserID: answerUserID,
|
||||||
TriggerUserID: commentUserID,
|
TriggerUserID: commentUserID,
|
||||||
|
@ -423,6 +482,46 @@ func (cs *CommentService) notificationAnswerComment(ctx context.Context, answerU
|
||||||
msg.ObjectType = constant.CommentObjectType
|
msg.ObjectType = constant.CommentObjectType
|
||||||
msg.NotificationAction = constant.CommentAnswer
|
msg.NotificationAction = constant.CommentAnswer
|
||||||
notice_queue.AddNotification(msg)
|
notice_queue.AddNotification(msg)
|
||||||
|
|
||||||
|
receiverUserInfo, exist, err := cs.userRepo.GetByUserID(ctx, answerUserID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !exist {
|
||||||
|
log.Warnf("user %s not found", answerUserID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if receiverUserInfo.NoticeStatus == schema.NoticeStatusOff || len(receiverUserInfo.EMail) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rawData := &schema.NewCommentTemplateRawData{
|
||||||
|
QuestionTitle: questionTitle,
|
||||||
|
QuestionID: questionID,
|
||||||
|
AnswerID: answerID,
|
||||||
|
CommentID: commentID,
|
||||||
|
CommentSummary: commentSummary,
|
||||||
|
UnsubscribeCode: encryption.MD5(receiverUserInfo.Pass),
|
||||||
|
}
|
||||||
|
commentUser, _, _ := cs.userCommon.GetUserBasicInfoByID(ctx, commentUserID)
|
||||||
|
if commentUser != nil {
|
||||||
|
rawData.CommentUserDisplayName = commentUser.DisplayName
|
||||||
|
}
|
||||||
|
codeContent := &schema.EmailCodeContent{
|
||||||
|
SourceType: schema.UnsubscribeSourceType,
|
||||||
|
Email: receiverUserInfo.EMail,
|
||||||
|
UserID: receiverUserInfo.ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
title, body, err := cs.emailService.NewCommentTemplate(ctx, rawData)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go cs.emailService.SendAndSaveCodeWithTime(
|
||||||
|
ctx, receiverUserInfo.EMail, title, body, rawData.UnsubscribeCode, codeContent.ToJSONString(), 7*24*time.Hour)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *CommentService) notificationCommentReply(ctx context.Context, replyUserID, commentID, commentUserID string) {
|
func (cs *CommentService) notificationCommentReply(ctx context.Context, replyUserID, commentID, commentUserID string) {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"mime"
|
"mime"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/base/reason"
|
"github.com/answerdev/answer/internal/base/reason"
|
||||||
"github.com/answerdev/answer/internal/entity"
|
"github.com/answerdev/answer/internal/entity"
|
||||||
|
@ -27,7 +28,7 @@ type EmailService struct {
|
||||||
|
|
||||||
// EmailRepo email repository
|
// EmailRepo email repository
|
||||||
type EmailRepo interface {
|
type EmailRepo interface {
|
||||||
SetCode(ctx context.Context, code, content string) error
|
SetCode(ctx context.Context, code, content string, duration time.Duration) error
|
||||||
VerifyCode(ctx context.Context, code string) (content string, err error)
|
VerifyCode(ctx context.Context, code string) (content string, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +60,10 @@ type EmailConfig struct {
|
||||||
ChangeBody string `json:"change_body"`
|
ChangeBody string `json:"change_body"`
|
||||||
TestTitle string `json:"test_title"`
|
TestTitle string `json:"test_title"`
|
||||||
TestBody string `json:"test_body"`
|
TestBody string `json:"test_body"`
|
||||||
|
NewAnswerTitle string `json:"new_answer_title"`
|
||||||
|
NewAnswerBody string `json:"new_answer_body"`
|
||||||
|
NewCommentTitle string `json:"new_comment_title"`
|
||||||
|
NewCommentBody string `json:"new_comment_body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *EmailConfig) IsSSL() bool {
|
func (e *EmailConfig) IsSSL() bool {
|
||||||
|
@ -84,8 +89,27 @@ type TestTemplateData struct {
|
||||||
SiteName string
|
SiteName string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendAndSaveCode send email and save code
|
||||||
|
func (es *EmailService) SendAndSaveCode(ctx context.Context, toEmailAddr, subject, body, code, codeContent string) {
|
||||||
|
es.Send(ctx, toEmailAddr, subject, body)
|
||||||
|
err := es.emailRepo.SetCode(ctx, code, codeContent, 10*time.Minute)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendAndSaveCodeWithTime send email and save code
|
||||||
|
func (es *EmailService) SendAndSaveCodeWithTime(
|
||||||
|
ctx context.Context, toEmailAddr, subject, body, code, codeContent string, duration time.Duration) {
|
||||||
|
es.Send(ctx, toEmailAddr, subject, body)
|
||||||
|
err := es.emailRepo.SetCode(ctx, code, codeContent, duration)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Send email send
|
// Send email send
|
||||||
func (es *EmailService) Send(ctx context.Context, toEmailAddr, subject, body, code, codeContent string) {
|
func (es *EmailService) Send(ctx context.Context, toEmailAddr, subject, body string) {
|
||||||
log.Infof("try to send email to %s", toEmailAddr)
|
log.Infof("try to send email to %s", toEmailAddr)
|
||||||
ec, err := es.GetEmailConfig()
|
ec, err := es.GetEmailConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -109,13 +133,6 @@ func (es *EmailService) Send(ctx context.Context, toEmailAddr, subject, body, co
|
||||||
} else {
|
} else {
|
||||||
log.Infof("send email to %s success", toEmailAddr)
|
log.Infof("send email to %s success", toEmailAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(code) > 0 {
|
|
||||||
err = es.emailRepo.SetCode(ctx, code, codeContent)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyUrlExpired email send
|
// VerifyUrlExpired email send
|
||||||
|
@ -250,41 +267,118 @@ func (es *EmailService) ChangeEmailTemplate(ctx context.Context, changeEmailUrl
|
||||||
return titleBuf.String(), bodyBuf.String(), nil
|
return titleBuf.String(), bodyBuf.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestTemplate send test email template parse
|
||||||
func (es *EmailService) TestTemplate(ctx context.Context) (title, body string, err error) {
|
func (es *EmailService) TestTemplate(ctx context.Context) (title, body string, err error) {
|
||||||
ec, err := es.GetEmailConfig()
|
emailConfig, err := es.GetEmailConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
siteinfo, err := es.GetSiteGeneral(ctx)
|
siteInfo, err := es.GetSiteGeneral(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
templateData := TestTemplateData{
|
templateData := TestTemplateData{
|
||||||
SiteName: siteinfo.Name,
|
SiteName: siteInfo.Name,
|
||||||
}
|
}
|
||||||
|
|
||||||
titleBuf := &bytes.Buffer{}
|
title, err = es.parseTemplateData(emailConfig.TestTitle, templateData)
|
||||||
bodyBuf := &bytes.Buffer{}
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("email template parse error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
tmpl, err := template.New("test_title").Parse(ec.TestTitle)
|
body, err = es.parseTemplateData(emailConfig.TestBody, templateData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("email test title template parse error: %s", err)
|
return "", "", fmt.Errorf("email template parse error: %s", err)
|
||||||
}
|
}
|
||||||
err = tmpl.Execute(titleBuf, templateData)
|
return title, body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAnswerTemplate new answer template
|
||||||
|
func (es *EmailService) NewAnswerTemplate(ctx context.Context, raw *schema.NewAnswerTemplateRawData) (
|
||||||
|
title, body string, err error) {
|
||||||
|
emailConfig, err := es.GetEmailConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("email test body template parse error: %s", err)
|
return
|
||||||
}
|
}
|
||||||
tmpl, err = template.New("test_body").Parse(ec.TestBody)
|
|
||||||
|
siteInfo, err := es.GetSiteGeneral(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("test_body template parse error: %s", err)
|
return
|
||||||
}
|
}
|
||||||
err = tmpl.Execute(bodyBuf, templateData)
|
templateData := &schema.NewAnswerTemplateData{
|
||||||
|
SiteName: siteInfo.Name,
|
||||||
|
DisplayName: raw.AnswerUserDisplayName,
|
||||||
|
QuestionTitle: raw.QuestionTitle,
|
||||||
|
AnswerUrl: fmt.Sprintf("%s/questions/%s/%s", siteInfo.SiteUrl, raw.QuestionID, raw.AnswerID),
|
||||||
|
AnswerSummary: raw.AnswerSummary,
|
||||||
|
UnsubscribeUrl: fmt.Sprintf("%s/users/unsubscribe?code=%s", siteInfo.SiteUrl, raw.UnsubscribeCode),
|
||||||
|
}
|
||||||
|
templateData.SiteName = siteInfo.Name
|
||||||
|
|
||||||
|
title, err = es.parseTemplateData(emailConfig.NewAnswerTitle, templateData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", fmt.Errorf("email template parse error: %s", err)
|
||||||
}
|
}
|
||||||
return titleBuf.String(), bodyBuf.String(), nil
|
|
||||||
|
body, err = es.parseTemplateData(emailConfig.NewAnswerBody, templateData)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("email template parse error: %s", err)
|
||||||
|
}
|
||||||
|
return title, body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCommentTemplate new comment template
|
||||||
|
func (es *EmailService) NewCommentTemplate(ctx context.Context, raw *schema.NewCommentTemplateRawData) (
|
||||||
|
title, body string, err error) {
|
||||||
|
emailConfig, err := es.GetEmailConfig()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
siteInfo, err := es.GetSiteGeneral(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
templateData := &schema.NewCommentTemplateData{
|
||||||
|
SiteName: siteInfo.Name,
|
||||||
|
DisplayName: raw.CommentUserDisplayName,
|
||||||
|
QuestionTitle: raw.QuestionTitle,
|
||||||
|
CommentSummary: raw.CommentSummary,
|
||||||
|
UnsubscribeUrl: fmt.Sprintf("%s/users/unsubscribe?code=%s", siteInfo.SiteUrl, raw.UnsubscribeCode),
|
||||||
|
}
|
||||||
|
if len(raw.AnswerID) > 0 {
|
||||||
|
templateData.CommentUrl = fmt.Sprintf("%s/questions/%s/%s?commentId=%s", siteInfo.SiteUrl, raw.QuestionID,
|
||||||
|
raw.AnswerID, raw.CommentID)
|
||||||
|
} else {
|
||||||
|
templateData.CommentUrl = fmt.Sprintf("%s/questions/%s?commentId=%s", siteInfo.SiteUrl,
|
||||||
|
raw.QuestionID, raw.CommentID)
|
||||||
|
}
|
||||||
|
templateData.SiteName = siteInfo.Name
|
||||||
|
|
||||||
|
title, err = es.parseTemplateData(emailConfig.NewCommentTitle, templateData)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("email template parse error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = es.parseTemplateData(emailConfig.NewCommentBody, templateData)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("email template parse error: %s", err)
|
||||||
|
}
|
||||||
|
return title, body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *EmailService) parseTemplateData(templateContent string, templateData interface{}) (parsedData string, err error) {
|
||||||
|
parsedDataBuf := &bytes.Buffer{}
|
||||||
|
tmpl, err := template.New("").Parse(templateContent)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
err = tmpl.Execute(parsedDataBuf, templateData)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return parsedDataBuf.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *EmailService) GetEmailConfig() (ec *EmailConfig, err error) {
|
func (es *EmailService) GetEmailConfig() (ec *EmailConfig, err error) {
|
||||||
|
|
|
@ -236,7 +236,7 @@ func (s *SiteInfoService) UpdateSMTPConfig(ctx context.Context, req *schema.Upda
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
go s.emailService.Send(ctx, req.TestEmailRecipient, title, body, "", "")
|
go s.emailService.SendAndSaveCode(ctx, req.TestEmailRecipient, title, body, "", "")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,7 +168,7 @@ func (us *UserService) RetrievePassWord(ctx context.Context, req *schema.UserRet
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
go us.emailService.Send(ctx, req.Email, title, body, code, data.ToJSONString())
|
go us.emailService.SendAndSaveCode(ctx, req.Email, title, body, code, data.ToJSONString())
|
||||||
return code, nil
|
return code, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,7 +333,7 @@ func (us *UserService) UserRegisterByEmail(ctx context.Context, registerUserInfo
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
go us.emailService.Send(ctx, userInfo.EMail, title, body, code, data.ToJSONString())
|
go us.emailService.SendAndSaveCode(ctx, userInfo.EMail, title, body, code, data.ToJSONString())
|
||||||
|
|
||||||
roleID, err := us.userRoleService.GetUserRole(ctx, userInfo.ID)
|
roleID, err := us.userRoleService.GetUserRole(ctx, userInfo.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -382,7 +382,7 @@ func (us *UserService) UserVerifyEmailSend(ctx context.Context, userID string) e
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
go us.emailService.Send(ctx, userInfo.EMail, title, body, code, data.ToJSONString())
|
go us.emailService.SendAndSaveCode(ctx, userInfo.EMail, title, body, code, data.ToJSONString())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -514,7 +514,7 @@ func (us *UserService) UserChangeEmailSendCode(ctx context.Context, req *schema.
|
||||||
}
|
}
|
||||||
log.Infof("send email confirmation %s", verifyEmailURL)
|
log.Infof("send email confirmation %s", verifyEmailURL)
|
||||||
|
|
||||||
go us.emailService.Send(context.Background(), req.Email, title, body, code, data.ToJSONString())
|
go us.emailService.SendAndSaveCode(context.Background(), req.Email, title, body, code, data.ToJSONString())
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -598,6 +598,25 @@ func (us *UserService) UserRanking(ctx context.Context) (resp *schema.UserRankin
|
||||||
return us.warpStatRankingResp(userInfoMapping, rankStat, voteStat, userRoleRels), nil
|
return us.warpStatRankingResp(userInfoMapping, rankStat, voteStat, userRoleRels), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserUnsubscribeEmailNotification user unsubscribe email notification
|
||||||
|
func (us *UserService) UserUnsubscribeEmailNotification(
|
||||||
|
ctx context.Context, req *schema.UserUnsubscribeEmailNotificationReq) (err error) {
|
||||||
|
data := &schema.EmailCodeContent{}
|
||||||
|
err = data.FromJSONString(req.Content)
|
||||||
|
if err != nil || len(data.UserID) == 0 {
|
||||||
|
return errors.BadRequest(reason.EmailVerifyURLExpired)
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo, exist, err := us.userRepo.GetByUserID(ctx, data.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !exist {
|
||||||
|
return errors.BadRequest(reason.UserNotFound)
|
||||||
|
}
|
||||||
|
return us.userRepo.UpdateNoticeStatus(ctx, userInfo.ID, schema.NoticeStatusOff)
|
||||||
|
}
|
||||||
|
|
||||||
func (us *UserService) getActivityUserRankStat(ctx context.Context, startTime, endTime time.Time, limit int,
|
func (us *UserService) getActivityUserRankStat(ctx context.Context, startTime, endTime time.Time, limit int,
|
||||||
userIDExist map[string]bool) (rankStat []*entity.ActivityUserRankStat, userIDs []string, err error) {
|
userIDExist map[string]bool) (rankStat []*entity.ActivityUserRankStat, userIDs []string, err error) {
|
||||||
rankStat, err = us.activityRepo.GetUsersWhoHasGainedTheMostReputation(ctx, startTime, endTime, limit)
|
rankStat, err = us.activityRepo.GetUsersWhoHasGainedTheMostReputation(ctx, startTime, endTime, limit)
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package encryption
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MD5 return md5 hash
|
||||||
|
func MD5(data string) string {
|
||||||
|
h := md5.New()
|
||||||
|
h.Write([]byte(data))
|
||||||
|
return hex.EncodeToString(h.Sum(nil))
|
||||||
|
}
|
Loading…
Reference in New Issue