diff --git a/.vscode/settings.json b/.vscode/settings.json index 93106f18..6c384d1e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "eslint.workingDirectories": [ "ui" - ] + ], + "commentTranslate.multiLineMerge": true } diff --git a/docs/docs.go b/docs/docs.go index 3c593476..3310c0b2 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -8925,6 +8925,14 @@ const docTemplate = `{ "pass" ], "properties": { + "captcha_code": { + "type": "string", + "maxLength": 500 + }, + "captcha_id": { + "type": "string", + "maxLength": 500 + }, "old_pass": { "type": "string", "maxLength": 32, diff --git a/docs/swagger.json b/docs/swagger.json index 963d6fe7..d8bcb632 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -8913,6 +8913,14 @@ "pass" ], "properties": { + "captcha_code": { + "type": "string", + "maxLength": 500 + }, + "captcha_id": { + "type": "string", + "maxLength": 500 + }, "old_pass": { "type": "string", "maxLength": 32, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index cb011241..a97803db 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2134,6 +2134,12 @@ definitions: type: object schema.UserModifyPasswordReq: properties: + captcha_code: + maxLength: 500 + type: string + captcha_id: + maxLength: 500 + type: string old_pass: maxLength: 32 minLength: 8 diff --git a/internal/controller/user_controller.go b/internal/controller/user_controller.go index 8c90ce6b..5266e66b 100644 --- a/internal/controller/user_controller.go +++ b/internal/controller/user_controller.go @@ -350,6 +350,21 @@ func (uc *UserController) UserModifyPassWord(ctx *gin.Context) { req.UserID = middleware.GetLoginUserIDFromContext(ctx) req.AccessToken = middleware.ExtractToken(ctx) + captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecordTypeModifyPass, ctx.ClientIP(), + req.CaptchaID, req.CaptchaCode) + if !captchaPass { + errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{ + ErrorField: "captcha_code", + ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed), + }) + handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields) + return + } + _, err := uc.actionService.ActionRecordAdd(ctx, schema.ActionRecordTypeModifyPass, ctx.ClientIP()) + if err != nil { + log.Error(err) + } + oldPassVerification, err := uc.userService.UserModifyPassWordVerification(ctx, req) if err != nil { handler.HandleResponse(ctx, err, nil) @@ -363,6 +378,7 @@ func (uc *UserController) UserModifyPassWord(ctx *gin.Context) { handler.HandleResponse(ctx, errors.BadRequest(reason.OldPasswordVerificationFailed), errFields) return } + if req.OldPass == req.Pass { errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{ ErrorField: "pass", @@ -372,6 +388,9 @@ func (uc *UserController) UserModifyPassWord(ctx *gin.Context) { return } err = uc.userService.UserModifyPassword(ctx, req) + if err == nil { + uc.actionService.ActionRecordDel(ctx, schema.ActionRecordTypeLogin, ctx.ClientIP()) + } handler.HandleResponse(ctx, err, nil) } diff --git a/internal/entity/tag_rel_entity.go b/internal/entity/tag_rel_entity.go index 859f8bf7..5f2992a8 100644 --- a/internal/entity/tag_rel_entity.go +++ b/internal/entity/tag_rel_entity.go @@ -4,6 +4,7 @@ import "time" const ( TagRelStatusAvailable = 1 + TagRelStatusHide = 2 TagRelStatusDeleted = 10 ) diff --git a/internal/migrations/v13.go b/internal/migrations/v13.go index 45c64ae7..0f18cd98 100644 --- a/internal/migrations/v13.go +++ b/internal/migrations/v13.go @@ -1,38 +1,23 @@ package migrations import ( - "encoding/json" "fmt" - "github.com/answerdev/answer/internal/base/constant" "github.com/answerdev/answer/internal/entity" - "github.com/answerdev/answer/internal/schema" "github.com/segmentfault/pacman/log" "xorm.io/xorm" ) -func addGravatarBaseURL(x *xorm.Engine) error { - usersSiteInfo := &entity.SiteInfo{ - Type: constant.SiteTypeUsers, - } - exist, err := x.Get(usersSiteInfo) - if err != nil { - return fmt.Errorf("get config failed: %w", err) - } - if exist { - content := &schema.SiteUsersReq{} - _ = json.Unmarshal([]byte(usersSiteInfo.Content), content) - content.GravatarBaseURL = "https://www.gravatar.com/avatar/" - data, _ := json.Marshal(content) - usersSiteInfo.Content = string(data) +func updateCount(x *xorm.Engine) error { + // updateQuestionCount(x) + // updateTagCount(x) + // updateUserQuestionCount(x) + updateUserAnswerCount(x) + return nil +} - _, err = x.ID(usersSiteInfo.ID).Cols("content").Update(usersSiteInfo) - if err != nil { - return fmt.Errorf("update site info failed: %w", err) - } - } - - //search all answers +func updateQuestionCount(x *xorm.Engine) error { + //question answer count answers := make([]entity.Answer, 0) err = x.Find(&answers, &entity.Answer{Status: entity.AnswerStatusAvailable}) if err != nil { @@ -62,5 +47,136 @@ func addGravatarBaseURL(x *xorm.Engine) error { } } } + + return nil +} + +// updateTagCount update tag count +func updateTagCount(x *xorm.Engine) error { + tagRelList := make([]entity.TagRel, 0) + err := x.Find(&tagRelList, &entity.TagRel{}) + if err != nil { + return fmt.Errorf("get tag rel failed: %w", err) + } + questionIDs := make([]string, 0) + questionsAvailableMap := make(map[string]bool) + questionsHideMap := make(map[string]bool) + for _, item := range tagRelList { + questionIDs = append(questionIDs, item.ObjectID) + questionsAvailableMap[item.ObjectID] = false + questionsHideMap[item.ObjectID] = false + } + questionList := make([]entity.Question, 0) + err = x.In("id", questionIDs).In("question.status", []int{entity.QuestionStatusAvailable, entity.QuestionStatusClosed}).Find(&questionList, &entity.Question{}) + if err != nil { + return fmt.Errorf("get questions failed: %w", err) + } + for _, question := range questionList { + _, ok := questionsAvailableMap[question.ID] + if ok { + questionsAvailableMap[question.ID] = true + if question.Show == entity.QuestionHide { + questionsHideMap[question.ID] = true + } + } + } + + for id, ok := range questionsHideMap { + if ok { + if _, err = x.Cols("status").Update(&entity.TagRel{Status: entity.TagRelStatusHide}, &entity.TagRel{ObjectID: id}); err != nil { + log.Errorf("update %+v config failed: %s", id, err) + } + } + } + + for id, ok := range questionsAvailableMap { + if !ok { + if _, err = x.Cols("status").Update(&entity.TagRel{Status: entity.TagRelStatusDeleted}, &entity.TagRel{ObjectID: id}); err != nil { + log.Errorf("update %+v config failed: %s", id, err) + } + } + } + + //select tag count + newTagRelList := make([]entity.TagRel, 0) + err = x.Find(&newTagRelList, &entity.TagRel{Status: entity.TagRelStatusAvailable}) + if err != nil { + return fmt.Errorf("get tag rel failed: %w", err) + } + tagCountMap := make(map[string]int) + for _, v := range newTagRelList { + _, ok := tagCountMap[v.TagID] + if !ok { + tagCountMap[v.TagID] = 1 + } else { + tagCountMap[v.TagID]++ + } + } + TagList := make([]entity.Tag, 0) + err = x.Find(&TagList, &entity.Tag{}) + if err != nil { + return fmt.Errorf("get tag failed: %w", err) + } + for _, tag := range TagList { + _, ok := tagCountMap[tag.ID] + if ok { + tag.QuestionCount = tagCountMap[tag.ID] + if _, err = x.Update(tag, &entity.Tag{ID: tag.ID}); err != nil { + log.Errorf("update %+v tag failed: %s", tag.ID, err) + return fmt.Errorf("update tag failed: %w", err) + } + } else { + tag.QuestionCount = 0 + if _, err = x.Update(tag, &entity.Tag{ID: tag.ID}); err != nil { + log.Errorf("update %+v tag failed: %s", tag.ID, err) + return fmt.Errorf("update tag failed: %w", err) + } + } + } + return nil +} + +// updateUserQuestionCount update user question count +func updateUserQuestionCount(x *xorm.Engine) error { + questionList := make([]entity.Question, 0) + err := x.In("status", []int{entity.QuestionStatusAvailable, entity.QuestionStatusClosed}).Find(&questionList, &entity.Question{}) + if err != nil { + return fmt.Errorf("get question failed: %w", err) + } + userQuestionCountMap := make(map[string]int) + for _, question := range questionList { + _, ok := userQuestionCountMap[question.UserID] + if !ok { + userQuestionCountMap[question.UserID] = 1 + } else { + userQuestionCountMap[question.UserID]++ + } + } + userList := make([]entity.User, 0) + err = x.Find(&userList, &entity.User{}) + if err != nil { + return fmt.Errorf("get user failed: %w", err) + } + for _, user := range userList { + _, ok := userQuestionCountMap[user.ID] + if ok { + user.QuestionCount = userQuestionCountMap[user.ID] + if _, err = x.Cols("question_count").Update(user, &entity.User{ID: user.ID}); err != nil { + log.Errorf("update %+v user failed: %s", user.ID, err) + return fmt.Errorf("update user failed: %w", err) + } + } else { + user.QuestionCount = 0 + if _, err = x.Cols("question_count").Update(user, &entity.User{ID: user.ID}); err != nil { + log.Errorf("update %+v user failed: %s", user.ID, err) + return fmt.Errorf("update user failed: %w", err) + } + } + } + return nil +} + +// updateUserAnswerCount update user answer count +func updateUserAnswerCount(x *xorm.Engine) error { return nil } diff --git a/internal/repo/rank/user_rank_repo.go b/internal/repo/rank/user_rank_repo.go index 462f999e..fc97c6de 100644 --- a/internal/repo/rank/user_rank_repo.go +++ b/internal/repo/rank/user_rank_repo.go @@ -138,7 +138,7 @@ func (ur *UserRankRepo) UserRankPage(ctx context.Context, userID string, page, p ) { rankPage = make([]*entity.Activity, 0) - session := ur.data.DB.Where(builder.Eq{"has_rank": 1}.And(builder.Eq{"cancelled": 0})) + session := ur.data.DB.Where(builder.Eq{"has_rank": 1}.And(builder.Eq{"cancelled": 0})).And(builder.Gt{"rank": 0}) session.Desc("created_at") cond := &entity.Activity{UserID: userID} diff --git a/internal/repo/tag/tag_rel_repo.go b/internal/repo/tag/tag_rel_repo.go index 22d26621..38cf353b 100644 --- a/internal/repo/tag/tag_rel_repo.go +++ b/internal/repo/tag/tag_rel_repo.go @@ -9,6 +9,7 @@ import ( tagcommon "github.com/answerdev/answer/internal/service/tag_common" "github.com/answerdev/answer/internal/service/unique" "github.com/answerdev/answer/pkg/uid" + "github.com/davecgh/go-spew/spew" "github.com/segmentfault/pacman/errors" ) @@ -52,6 +53,26 @@ func (tr *tagRelRepo) RemoveTagRelListByObjectID(ctx context.Context, objectID s return } +func (tr *tagRelRepo) HideTagRelListByObjectID(ctx context.Context, objectID string) (err error) { + spew.Dump("====== HideTagRelListByObjectID") + objectID = uid.DeShortID(objectID) + _, err = tr.data.DB.Where("object_id = ?", objectID).Cols("status").Update(&entity.TagRel{Status: entity.TagRelStatusHide}) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + +func (tr *tagRelRepo) ShowTagRelListByObjectID(ctx context.Context, objectID string) (err error) { + spew.Dump("====== ShowTagRelListByObjectID") + objectID = uid.DeShortID(objectID) + _, err = tr.data.DB.Where("object_id = ?", objectID).Cols("status").Update(&entity.TagRel{Status: entity.TagRelStatusAvailable}) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return +} + // RemoveTagRelListByIDs delete tag list func (tr *tagRelRepo) RemoveTagRelListByIDs(ctx context.Context, ids []int64) (err error) { _, err = tr.data.DB.In("id", ids).Update(&entity.TagRel{Status: entity.TagRelStatusDeleted}) @@ -90,7 +111,7 @@ func (tr *tagRelRepo) GetObjectTagRelList(ctx context.Context, objectID string) objectID = uid.DeShortID(objectID) tagListList = make([]*entity.TagRel, 0) session := tr.data.DB.Where("object_id = ?", objectID) - session.Where("status = ?", entity.TagRelStatusAvailable) + session.In("status", []int{entity.TagRelStatusAvailable, entity.TagRelStatusHide}) err = session.Find(&tagListList) if err != nil { err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() diff --git a/internal/schema/user_schema.go b/internal/schema/user_schema.go index f1f2bc2f..0a5203b7 100644 --- a/internal/schema/user_schema.go +++ b/internal/schema/user_schema.go @@ -222,9 +222,10 @@ const ( NoticeStatusOn = 1 NoticeStatusOff = 2 - ActionRecordTypeLogin = "login" - ActionRecordTypeEmail = "e_mail" - ActionRecordTypeFindPass = "find_pass" + ActionRecordTypeLogin = "login" + ActionRecordTypeEmail = "e_mail" + ActionRecordTypeFindPass = "find_pass" + ActionRecordTypeModifyPass = "modify_pass" ) var UserStatusShow = map[int]string{ @@ -276,10 +277,12 @@ func (u *UserRegisterReq) Check() (errFields []*validator.FormErrorField, err er } type UserModifyPasswordReq struct { - OldPass string `validate:"omitempty,gte=8,lte=32" json:"old_pass"` - Pass string `validate:"required,gte=8,lte=32" json:"pass"` + OldPass string `validate:"omitempty,gte=8,lte=32" json:"old_pass"` + Pass string `validate:"required,gte=8,lte=32" json:"pass"` UserID string `json:"-"` AccessToken string `json:"-"` + CaptchaID string `validate:"omitempty,gt=0,lte=500" json:"captcha_id"` + CaptchaCode string `validate:"omitempty,gt=0,lte=500" json:"captcha_code"` } func (u *UserModifyPasswordReq) Check() (errFields []*validator.FormErrorField, err error) { @@ -376,7 +379,7 @@ type UserNoticeSetResp struct { type ActionRecordReq struct { // action - Action string `validate:"required,oneof=login e_mail find_pass" form:"action"` + Action string `validate:"required,oneof=login e_mail find_pass modify_pass" form:"action"` IP string `json:"-"` } diff --git a/internal/service/activity_type/activity_type.go b/internal/service/activity_type/activity_type.go index 3b86799e..22ab8490 100644 --- a/internal/service/activity_type/activity_type.go +++ b/internal/service/activity_type/activity_type.go @@ -1,14 +1,22 @@ package activity_type -import "github.com/answerdev/answer/internal/repo/config" +import ( + "github.com/answerdev/answer/internal/repo/config" +) const ( - QuestionVoteUp = "question.vote_up" - QuestionVoteDown = "question.vote_down" - AnswerVoteUp = "answer.vote_up" - AnswerVoteDown = "answer.vote_down" - CommentVoteUp = "comment.vote_up" - CommentVoteDown = "comment.vote_down" + QuestionVoteUp = "question.vote_up" + QuestionVoteDown = "question.vote_down" + AnswerVoteUp = "answer.vote_up" + AnswerVoteDown = "answer.vote_down" + CommentVoteUp = "comment.vote_up" + CommentVoteDown = "comment.vote_down" + AnswerAccepted = "answer.accepted" + AnswerAccept = "answer.accept" + QuestionVotedUp = "question.voted_up" + QuestionVotedDown = "question.voted_down" + AnswerVotedUp = "answer.voted_up" + AnswerVotedDown = "answer.voted_down" ) var ( @@ -19,14 +27,26 @@ var ( AnswerVoteDown, CommentVoteUp, CommentVoteDown, + AnswerAccepted, + AnswerAccept, + QuestionVotedUp, + QuestionVotedDown, + AnswerVotedUp, + AnswerVotedDown, } activityTypeFlagMapping = map[string]string{ - QuestionVoteUp: "upvote", - QuestionVoteDown: "downvote", - AnswerVoteUp: "upvote", - AnswerVoteDown: "downvote", - CommentVoteUp: "upvote", - CommentVoteDown: "downvote", + QuestionVoteUp: "upvote", + QuestionVoteDown: "downvote", + AnswerVoteUp: "upvote", + AnswerVoteDown: "downvote", + CommentVoteUp: "upvote", + CommentVoteDown: "downvote", + AnswerAccepted: "accepted", + AnswerAccept: "accept", + QuestionVotedUp: "upvoted", + QuestionVotedDown: "downvoted", + AnswerVotedUp: "upvoted", + AnswerVotedDown: "downvoted", } ) diff --git a/internal/service/question_service.go b/internal/service/question_service.go index b47ffba9..a1377882 100644 --- a/internal/service/question_service.go +++ b/internal/service/question_service.go @@ -349,8 +349,24 @@ func (qs *QuestionService) OperationQuestion(ctx context.Context, req *schema.Op switch req.Operation { case schema.QuestionOperationHide: questionInfo.Show = entity.QuestionHide + err = qs.tagCommon.HideTagRelListByObjectID(ctx, req.ID) + if err != nil { + return err + } + err = qs.tagCommon.RefreshTagCountByQuestionID(ctx, req.ID) + if err != nil { + return err + } case schema.QuestionOperationShow: questionInfo.Show = entity.QuestionShow + err = qs.tagCommon.ShowTagRelListByObjectID(ctx, req.ID) + if err != nil { + return err + } + err = qs.tagCommon.RefreshTagCountByQuestionID(ctx, req.ID) + if err != nil { + return err + } case schema.QuestionOperationPin: questionInfo.Pin = entity.QuestionPin case schema.QuestionOperationUnPin: @@ -436,6 +452,25 @@ func (qs *QuestionService) RemoveQuestion(ctx context.Context, req *schema.Remov } } + //tag count + tagIDs := make([]string, 0) + Tags, tagerr := qs.tagCommon.GetObjectEntityTag(ctx, req.ID) + if tagerr != nil { + log.Error("GetObjectEntityTag error", tagerr) + return nil + } + for _, v := range Tags { + tagIDs = append(tagIDs, v.ID) + } + err = qs.tagCommon.RemoveTagRelListByObjectID(ctx, req.ID) + if err != nil { + log.Error("RemoveTagRelListByObjectID error", err.Error()) + } + err = qs.tagCommon.RefreshTagQuestionCount(ctx, tagIDs) + if err != nil { + log.Error("efreshTagQuestionCount error", err.Error()) + } + err = qs.answerActivityService.DeleteQuestion(ctx, questionInfo.ID, questionInfo.CreatedAt, questionInfo.VoteCount) if err != nil { log.Errorf("user DeleteQuestion rank rollback error %s", err.Error()) diff --git a/internal/service/tag_common/tag_common.go b/internal/service/tag_common/tag_common.go index c136838e..41350b14 100644 --- a/internal/service/tag_common/tag_common.go +++ b/internal/service/tag_common/tag_common.go @@ -44,6 +44,9 @@ type TagRepo interface { type TagRelRepo interface { AddTagRelList(ctx context.Context, tagList []*entity.TagRel) (err error) + RemoveTagRelListByObjectID(ctx context.Context, objectID string) (err error) + ShowTagRelListByObjectID(ctx context.Context, objectID string) (err error) + HideTagRelListByObjectID(ctx context.Context, objectID string) (err error) RemoveTagRelListByIDs(ctx context.Context, ids []int64) (err error) EnableTagRelByIDs(ctx context.Context, ids []int64) (err error) GetObjectTagRelWithoutStatus(ctx context.Context, objectId, tagID string) (tagRel *entity.TagRel, exist bool, err error) @@ -653,6 +656,35 @@ func (ts *TagCommonService) RefreshTagQuestionCount(ctx context.Context, tagIDs return nil } +func (ts *TagCommonService) RefreshTagCountByQuestionID(ctx context.Context, questionID string) (err error) { + tagListList, err := ts.tagRelRepo.GetObjectTagRelList(ctx, questionID) + if err != nil { + return err + } + tagIDs := make([]string, 0) + for _, item := range tagListList { + tagIDs = append(tagIDs, item.TagID) + } + err = ts.RefreshTagQuestionCount(ctx, tagIDs) + if err != nil { + return err + } + return nil +} + +// RemoveTagRelListByObjectID remove tag relation by object id +func (ts *TagCommonService) RemoveTagRelListByObjectID(ctx context.Context, objectID string) (err error) { + return ts.tagRelRepo.RemoveTagRelListByObjectID(ctx, objectID) +} + +func (ts *TagCommonService) HideTagRelListByObjectID(ctx context.Context, objectID string) (err error) { + return ts.tagRelRepo.HideTagRelListByObjectID(ctx, objectID) +} + +func (ts *TagCommonService) ShowTagRelListByObjectID(ctx context.Context, objectID string) (err error) { + return ts.tagRelRepo.ShowTagRelListByObjectID(ctx, objectID) +} + // CreateOrUpdateTagRelList if tag relation is exists update status, if not create it func (ts *TagCommonService) CreateOrUpdateTagRelList(ctx context.Context, objectId string, tagIDs []string) (err error) { addTagIDMapping := make(map[string]bool)