feat(user): remove all the content when delete user

This commit is contained in:
LinkinStars 2023-09-12 18:56:52 +08:00
parent 133907b59e
commit b38f2e4345
12 changed files with 141 additions and 18 deletions

View File

@ -194,7 +194,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
reportAdminService := report_admin.NewReportAdminService(reportRepo, userCommon, answerRepo, questionRepo, commentCommonRepo, reportHandle, configService, objService)
controller_adminReportController := controller_admin.NewReportController(reportAdminService)
userAdminRepo := user.NewUserAdminRepo(dataData, authRepo)
userAdminService := user_admin.NewUserAdminService(userAdminRepo, userRoleRelService, authService, userCommon, userActiveActivityRepo, siteInfoCommonService, emailService)
userAdminService := user_admin.NewUserAdminService(userAdminRepo, userRoleRelService, authService, userCommon, userActiveActivityRepo, siteInfoCommonService, emailService, questionRepo, answerRepo, commentCommonRepo)
userAdminController := controller_admin.NewUserAdminController(userAdminService)
reasonRepo := reason.NewReasonRepo(configService)
reasonService := reason2.NewReasonService(reasonRepo)

View File

@ -3,6 +3,7 @@ package answer
import (
"context"
"github.com/answerdev/answer/plugin"
"github.com/segmentfault/pacman/log"
"time"
"github.com/answerdev/answer/internal/base/constant"
@ -79,6 +80,40 @@ func (ar *answerRepo) RemoveAnswer(ctx context.Context, id string) (err error) {
return nil
}
// RemoveAllUserAnswer remove all user answer
func (ar *answerRepo) RemoveAllUserAnswer(ctx context.Context, userID string) (err error) {
// find all answer id that need to be deleted
answerIDs := make([]string, 0)
session := ar.data.DB.Context(ctx).Where("user_id = ?", userID)
session.Where("status != ?", entity.AnswerStatusDeleted)
err = session.Select("id").Table("answer").Find(&answerIDs)
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
if len(answerIDs) == 0 {
return nil
}
log.Infof("find %d answers need to be deleted for user %s", len(answerIDs), userID)
// delete all question
session = ar.data.DB.Context(ctx).Where("user_id = ?", userID)
session.Where("status != ?", entity.AnswerStatusDeleted)
_, err = session.Cols("status", "updated_at").Update(&entity.Answer{
UpdatedAt: time.Now(),
Status: entity.AnswerStatusDeleted,
})
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
// update search content
for _, id := range answerIDs {
_ = ar.updateSearch(ctx, id)
}
return nil
}
// UpdateAnswer update answer
func (ar *answerRepo) UpdateAnswer(ctx context.Context, answer *entity.Answer, Colar []string) (err error) {
answer.ID = uid.DeShortID(answer.ID)

View File

@ -2,6 +2,7 @@ package comment
import (
"context"
"github.com/segmentfault/pacman/log"
"github.com/answerdev/answer/internal/base/data"
"github.com/answerdev/answer/internal/base/pager"
@ -108,3 +109,15 @@ func (cr *commentRepo) GetCommentPage(ctx context.Context, commentQuery *comment
}
return
}
// RemoveAllUserComment remove all user comment
func (cr *commentRepo) RemoveAllUserComment(ctx context.Context, userID string) (err error) {
session := cr.data.DB.Context(ctx).Where("user_id = ?", userID)
session.Where("status != ?", entity.CommentStatusDeleted)
affected, err := session.Update(&entity.Comment{Status: entity.CommentStatusDeleted})
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
log.Infof("delete user comment, userID: %s, affected: %d", userID, affected)
return
}

View File

@ -475,3 +475,36 @@ func (qr *questionRepo) updateSearch(ctx context.Context, questionID string) (er
err = s.UpdateContent(ctx, content)
return
}
func (qr *questionRepo) RemoveAllUserQuestion(ctx context.Context, userID string) (err error) {
// get all question id that need to be deleted
questionIDs := make([]string, 0)
session := qr.data.DB.Context(ctx).Where("user_id = ?", userID)
session.Where("status != ?", entity.QuestionStatusDeleted)
err = session.Select("id").Table("question").Find(&questionIDs)
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
if len(questionIDs) == 0 {
return nil
}
log.Infof("find %d questions need to be deleted for user %s", len(questionIDs), userID)
// delete all question
session = qr.data.DB.Context(ctx).Where("user_id = ?", userID)
session.Where("status != ?", entity.QuestionStatusDeleted)
_, err = session.Cols("status", "updated_at").Update(&entity.Question{
UpdatedAt: time.Now(),
Status: entity.QuestionStatusDeleted,
})
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
// update search content
for _, id := range questionIDs {
_ = qr.updateSearch(ctx, id)
}
return nil
}

View File

@ -13,9 +13,10 @@ import (
// UpdateUserStatusReq update user request
type UpdateUserStatusReq struct {
UserID string `validate:"required" json:"user_id"`
Status string `validate:"required,oneof=normal suspended deleted inactive" json:"status" enums:"normal,suspended,deleted,inactive"`
LoginUserID string `json:"-"`
UserID string `validate:"required" json:"user_id"`
Status string `validate:"required,oneof=normal suspended deleted inactive" json:"status" enums:"normal,suspended,deleted,inactive"`
RemoveAllContent bool `validate:"omitempty" json:"remove_all_content"`
LoginUserID string `json:"-"`
}
func (r *UpdateUserStatusReq) IsNormal() bool { return r.Status == constant.UserNormal }

View File

@ -26,6 +26,7 @@ type AnswerRepo interface {
AdminSearchList(ctx context.Context, search *schema.AdminAnswerPageReq) ([]*entity.Answer, int64, error)
UpdateAnswerStatus(ctx context.Context, answer *entity.Answer) (err error)
GetAnswerCount(ctx context.Context) (count int64, err error)
RemoveAllUserAnswer(ctx context.Context, userID string) (err error)
}
// AnswerCommon user service

View File

@ -13,6 +13,7 @@ import (
type CommentCommonRepo interface {
GetComment(ctx context.Context, commentID string) (comment *entity.Comment, exist bool, err error)
GetCommentCount(ctx context.Context) (count int64, err error)
RemoveAllUserComment(ctx context.Context, userID string) (err error)
}
// CommentCommonService user service

View File

@ -51,6 +51,7 @@ type QuestionRepo interface {
GetQuestionCount(ctx context.Context) (count int64, err error)
GetUserQuestionCount(ctx context.Context, userID string) (count int64, err error)
SitemapQuestions(ctx context.Context, page, pageSize int) (questionIDList []*schema.SiteMapQuestionInfo, err error)
RemoveAllUserQuestion(ctx context.Context, userID string) (err error)
}
// QuestionCommon user service

View File

@ -27,7 +27,7 @@ type SiteInfoCommonService interface {
GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceResp, err error)
GetSiteBranding(ctx context.Context) (resp *schema.SiteBrandingResp, err error)
GetSiteUsers(ctx context.Context) (resp *schema.SiteUsersResp, err error)
FormatAvatar(ctx context.Context, originalAvatarData, email string) *schema.AvatarInfo
FormatAvatar(ctx context.Context, originalAvatarData, email string, userStatus int) *schema.AvatarInfo
FormatListAvatar(ctx context.Context, userList []*entity.User) (userID2AvatarMapping map[string]*schema.AvatarInfo)
GetSiteWrite(ctx context.Context) (resp *schema.SiteWriteResp, err error)
GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, err error)
@ -82,9 +82,9 @@ func (s *siteInfoCommonService) GetSiteUsers(ctx context.Context) (resp *schema.
}
// FormatAvatar format avatar
func (s *siteInfoCommonService) FormatAvatar(ctx context.Context, originalAvatarData, email string) *schema.AvatarInfo {
func (s *siteInfoCommonService) FormatAvatar(ctx context.Context, originalAvatarData, email string, userStatus int) *schema.AvatarInfo {
gravatarBaseURL, defaultAvatar := s.getAvatarDefaultConfig(ctx)
return s.selectedAvatar(originalAvatarData, defaultAvatar, gravatarBaseURL, email)
return s.selectedAvatar(originalAvatarData, defaultAvatar, gravatarBaseURL, email, userStatus)
}
// FormatListAvatar format avatar
@ -93,7 +93,7 @@ func (s *siteInfoCommonService) FormatListAvatar(ctx context.Context, userList [
gravatarBaseURL, defaultAvatar := s.getAvatarDefaultConfig(ctx)
avatarMapping = make(map[string]*schema.AvatarInfo)
for _, user := range userList {
avatarMapping[user.ID] = s.selectedAvatar(user.Avatar, defaultAvatar, gravatarBaseURL, user.EMail)
avatarMapping[user.ID] = s.selectedAvatar(user.Avatar, defaultAvatar, gravatarBaseURL, user.EMail, user.Status)
}
return avatarMapping
}
@ -114,10 +114,18 @@ func (s *siteInfoCommonService) getAvatarDefaultConfig(ctx context.Context) (str
}
func (s *siteInfoCommonService) selectedAvatar(
originalAvatarData string, defaultAvatar string, gravatarBaseURL string, email string) *schema.AvatarInfo {
originalAvatarData,
defaultAvatar, gravatarBaseURL,
email string, userStatus int) *schema.AvatarInfo {
avatarInfo := &schema.AvatarInfo{}
_ = json.Unmarshal([]byte(originalAvatarData), avatarInfo)
if userStatus == entity.UserStatusDeleted {
return &schema.AvatarInfo{
Type: constant.DefaultAvatar,
}
}
if len(avatarInfo.Type) == 0 && defaultAvatar == constant.AvatarTypeGravatar {
avatarInfo.Type = constant.AvatarTypeGravatar
avatarInfo.Gravatar = gravatar.GetAvatarURL(gravatarBaseURL, email)

View File

@ -7,7 +7,10 @@ import (
"github.com/answerdev/answer/internal/base/handler"
"github.com/answerdev/answer/internal/base/translator"
"github.com/answerdev/answer/internal/base/validator"
answercommon "github.com/answerdev/answer/internal/service/answer_common"
"github.com/answerdev/answer/internal/service/comment_common"
"github.com/answerdev/answer/internal/service/export"
questioncommon "github.com/answerdev/answer/internal/service/question_common"
"github.com/google/uuid"
"net/mail"
"strings"
@ -50,6 +53,9 @@ type UserAdminService struct {
userActivity activity.UserActiveActivityRepo
siteInfoCommonService siteinfo_common.SiteInfoCommonService
emailService *export.EmailService
questionCommonRepo questioncommon.QuestionRepo
answerCommonRepo answercommon.AnswerRepo
commentCommonRepo comment_common.CommentCommonRepo
}
// NewUserAdminService new user admin service
@ -61,6 +67,9 @@ func NewUserAdminService(
userActivity activity.UserActiveActivityRepo,
siteInfoCommonService siteinfo_common.SiteInfoCommonService,
emailService *export.EmailService,
questionCommonRepo questioncommon.QuestionRepo,
answerCommonRepo answercommon.AnswerRepo,
commentCommonRepo comment_common.CommentCommonRepo,
) *UserAdminService {
return &UserAdminService{
userRepo: userRepo,
@ -70,6 +79,9 @@ func NewUserAdminService(
userActivity: userActivity,
siteInfoCommonService: siteInfoCommonService,
emailService: emailService,
questionCommonRepo: questionCommonRepo,
answerCommonRepo: answerCommonRepo,
commentCommonRepo: commentCommonRepo,
}
}
@ -111,6 +123,11 @@ func (us *UserAdminService) UpdateUserStatus(ctx context.Context, req *schema.Up
return err
}
// remove all content that user created, such as question, answer, comment, etc.
if req.RemoveAllContent {
us.removeAllUserCreatedContent(ctx, userInfo.ID)
}
// if user reputation is zero means this user is inactive, so try to activate this user.
if req.IsNormal() && userInfo.Rank == 0 {
return us.userActivity.UserActive(ctx, userInfo.ID)
@ -118,6 +135,19 @@ func (us *UserAdminService) UpdateUserStatus(ctx context.Context, req *schema.Up
return nil
}
// removeAllUserCreatedContent remove all user created content
func (us *UserAdminService) removeAllUserCreatedContent(ctx context.Context, userID string) {
if err := us.questionCommonRepo.RemoveAllUserQuestion(ctx, userID); err != nil {
log.Errorf("remove all user question error: %v", err)
}
if err := us.answerCommonRepo.RemoveAllUserAnswer(ctx, userID); err != nil {
log.Errorf("remove all user answer error: %v", err)
}
if err := us.commentCommonRepo.RemoveAllUserComment(ctx, userID); err != nil {
log.Errorf("remove all user comment error: %v", err)
}
}
// UpdateUserRole update user role
func (us *UserAdminService) UpdateUserRole(ctx context.Context, req *schema.UpdateUserRoleReq) (err error) {
// Users cannot modify their roles

View File

@ -69,7 +69,7 @@ func (us *UserCommon) GetUserBasicInfoByID(ctx context.Context, ID string) (
return nil, exist, err
}
info := us.FormatUserBasicInfo(ctx, userInfo)
info.Avatar = us.siteInfoCommonService.FormatAvatar(ctx, userInfo.Avatar, userInfo.EMail).GetURL()
info.Avatar = us.siteInfoCommonService.FormatAvatar(ctx, userInfo.Avatar, userInfo.EMail, userInfo.Status).GetURL()
return info, exist, nil
}
@ -79,7 +79,7 @@ func (us *UserCommon) GetUserBasicInfoByUserName(ctx context.Context, username s
return nil, exist, err
}
info := us.FormatUserBasicInfo(ctx, userInfo)
info.Avatar = us.siteInfoCommonService.FormatAvatar(ctx, userInfo.Avatar, userInfo.EMail).GetURL()
info.Avatar = us.siteInfoCommonService.FormatAvatar(ctx, userInfo.Avatar, userInfo.EMail, userInfo.Status).GetURL()
return info, exist, nil
}
@ -137,7 +137,7 @@ func (us *UserCommon) FormatUserBasicInfo(ctx context.Context, userInfo *entity.
userBasicInfo.Status = constant.ConvertUserStatus(userInfo.Status, userInfo.MailStatus)
if userBasicInfo.Status == constant.UserDeleted {
userBasicInfo.Avatar = ""
userBasicInfo.DisplayName = "Anonymous"
userBasicInfo.DisplayName = "user" + userInfo.ID
}
return userBasicInfo
}

View File

@ -92,7 +92,7 @@ func (us *UserService) GetUserInfoByUserID(ctx context.Context, token, userID st
if err != nil {
log.Error(err)
}
resp.Avatar = us.siteInfoService.FormatAvatar(ctx, userInfo.Avatar, userInfo.EMail)
resp.Avatar = us.siteInfoService.FormatAvatar(ctx, userInfo.Avatar, userInfo.EMail, userInfo.Status)
resp.AccessToken = token
resp.HavePassword = len(userInfo.Pass) > 0
return resp, nil
@ -109,7 +109,7 @@ func (us *UserService) GetOtherUserInfoByUsername(ctx context.Context, username
}
resp = &schema.GetOtherUserInfoByUsernameResp{}
resp.ConvertFromUserEntity(userInfo)
resp.Avatar = us.siteInfoService.FormatAvatar(ctx, userInfo.Avatar, userInfo.EMail).GetURL()
resp.Avatar = us.siteInfoService.FormatAvatar(ctx, userInfo.Avatar, userInfo.EMail, userInfo.Status).GetURL()
return resp, nil
}
@ -145,7 +145,7 @@ func (us *UserService) EmailLogin(ctx context.Context, req *schema.UserEmailLogi
resp = &schema.UserLoginResp{}
resp.ConvertFromUserEntity(userInfo)
resp.Avatar = us.siteInfoService.FormatAvatar(ctx, userInfo.Avatar, userInfo.EMail).GetURL()
resp.Avatar = us.siteInfoService.FormatAvatar(ctx, userInfo.Avatar, userInfo.EMail, userInfo.Status).GetURL()
userCacheInfo := &entity.UserCacheInfo{
UserID: userInfo.ID,
EmailStatus: userInfo.MailStatus,
@ -426,7 +426,7 @@ func (us *UserService) UserRegisterByEmail(ctx context.Context, registerUserInfo
// return user info and token
resp = &schema.UserLoginResp{}
resp.ConvertFromUserEntity(userInfo)
resp.Avatar = us.siteInfoService.FormatAvatar(ctx, userInfo.Avatar, userInfo.EMail).GetURL()
resp.Avatar = us.siteInfoService.FormatAvatar(ctx, userInfo.Avatar, userInfo.EMail, userInfo.Status).GetURL()
userCacheInfo := &entity.UserCacheInfo{
UserID: userInfo.ID,
EmailStatus: userInfo.MailStatus,
@ -511,7 +511,7 @@ func (us *UserService) UserVerifyEmail(ctx context.Context, req *schema.UserVeri
resp = &schema.UserLoginResp{}
resp.ConvertFromUserEntity(userInfo)
resp.Avatar = us.siteInfoService.FormatAvatar(ctx, userInfo.Avatar, userInfo.EMail).GetURL()
resp.Avatar = us.siteInfoService.FormatAvatar(ctx, userInfo.Avatar, userInfo.EMail, userInfo.Status).GetURL()
resp.AccessToken = accessToken
// User verified email will update user email status. So user status cache should be updated.
if err = us.authService.SetUserStatus(ctx, userCacheInfo); err != nil {
@ -630,7 +630,7 @@ func (us *UserService) UserChangeEmailVerify(ctx context.Context, content string
resp = &schema.UserLoginResp{}
resp.ConvertFromUserEntity(userInfo)
resp.Avatar = us.siteInfoService.FormatAvatar(ctx, userInfo.Avatar, userInfo.EMail).GetURL()
resp.Avatar = us.siteInfoService.FormatAvatar(ctx, userInfo.Avatar, userInfo.EMail, userInfo.Status).GetURL()
userCacheInfo := &entity.UserCacheInfo{
UserID: userInfo.ID,
EmailStatus: entity.EmailStatusAvailable,