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) reportAdminService := report_admin.NewReportAdminService(reportRepo, userCommon, answerRepo, questionRepo, commentCommonRepo, reportHandle, configService, objService)
controller_adminReportController := controller_admin.NewReportController(reportAdminService) controller_adminReportController := controller_admin.NewReportController(reportAdminService)
userAdminRepo := user.NewUserAdminRepo(dataData, authRepo) 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) userAdminController := controller_admin.NewUserAdminController(userAdminService)
reasonRepo := reason.NewReasonRepo(configService) reasonRepo := reason.NewReasonRepo(configService)
reasonService := reason2.NewReasonService(reasonRepo) reasonService := reason2.NewReasonService(reasonRepo)

View File

@ -3,6 +3,7 @@ package answer
import ( import (
"context" "context"
"github.com/answerdev/answer/plugin" "github.com/answerdev/answer/plugin"
"github.com/segmentfault/pacman/log"
"time" "time"
"github.com/answerdev/answer/internal/base/constant" "github.com/answerdev/answer/internal/base/constant"
@ -79,6 +80,40 @@ func (ar *answerRepo) RemoveAnswer(ctx context.Context, id string) (err error) {
return nil 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 // UpdateAnswer update answer
func (ar *answerRepo) UpdateAnswer(ctx context.Context, answer *entity.Answer, Colar []string) (err error) { func (ar *answerRepo) UpdateAnswer(ctx context.Context, answer *entity.Answer, Colar []string) (err error) {
answer.ID = uid.DeShortID(answer.ID) answer.ID = uid.DeShortID(answer.ID)

View File

@ -2,6 +2,7 @@ package comment
import ( import (
"context" "context"
"github.com/segmentfault/pacman/log"
"github.com/answerdev/answer/internal/base/data" "github.com/answerdev/answer/internal/base/data"
"github.com/answerdev/answer/internal/base/pager" "github.com/answerdev/answer/internal/base/pager"
@ -108,3 +109,15 @@ func (cr *commentRepo) GetCommentPage(ctx context.Context, commentQuery *comment
} }
return 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) err = s.UpdateContent(ctx, content)
return 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 // UpdateUserStatusReq update user request
type UpdateUserStatusReq struct { type UpdateUserStatusReq struct {
UserID string `validate:"required" json:"user_id"` UserID string `validate:"required" json:"user_id"`
Status string `validate:"required,oneof=normal suspended deleted inactive" json:"status" enums:"normal,suspended,deleted,inactive"` Status string `validate:"required,oneof=normal suspended deleted inactive" json:"status" enums:"normal,suspended,deleted,inactive"`
LoginUserID string `json:"-"` RemoveAllContent bool `validate:"omitempty" json:"remove_all_content"`
LoginUserID string `json:"-"`
} }
func (r *UpdateUserStatusReq) IsNormal() bool { return r.Status == constant.UserNormal } 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) AdminSearchList(ctx context.Context, search *schema.AdminAnswerPageReq) ([]*entity.Answer, int64, error)
UpdateAnswerStatus(ctx context.Context, answer *entity.Answer) (err error) UpdateAnswerStatus(ctx context.Context, answer *entity.Answer) (err error)
GetAnswerCount(ctx context.Context) (count int64, err error) GetAnswerCount(ctx context.Context) (count int64, err error)
RemoveAllUserAnswer(ctx context.Context, userID string) (err error)
} }
// AnswerCommon user service // AnswerCommon user service

View File

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

View File

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

View File

@ -27,7 +27,7 @@ type SiteInfoCommonService interface {
GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceResp, err error) GetSiteInterface(ctx context.Context) (resp *schema.SiteInterfaceResp, err error)
GetSiteBranding(ctx context.Context) (resp *schema.SiteBrandingResp, err error) GetSiteBranding(ctx context.Context) (resp *schema.SiteBrandingResp, err error)
GetSiteUsers(ctx context.Context) (resp *schema.SiteUsersResp, 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) FormatListAvatar(ctx context.Context, userList []*entity.User) (userID2AvatarMapping map[string]*schema.AvatarInfo)
GetSiteWrite(ctx context.Context) (resp *schema.SiteWriteResp, err error) GetSiteWrite(ctx context.Context) (resp *schema.SiteWriteResp, err error)
GetSiteLegal(ctx context.Context) (resp *schema.SiteLegalResp, 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 // 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) gravatarBaseURL, defaultAvatar := s.getAvatarDefaultConfig(ctx)
return s.selectedAvatar(originalAvatarData, defaultAvatar, gravatarBaseURL, email) return s.selectedAvatar(originalAvatarData, defaultAvatar, gravatarBaseURL, email, userStatus)
} }
// FormatListAvatar format avatar // FormatListAvatar format avatar
@ -93,7 +93,7 @@ func (s *siteInfoCommonService) FormatListAvatar(ctx context.Context, userList [
gravatarBaseURL, defaultAvatar := s.getAvatarDefaultConfig(ctx) gravatarBaseURL, defaultAvatar := s.getAvatarDefaultConfig(ctx)
avatarMapping = make(map[string]*schema.AvatarInfo) avatarMapping = make(map[string]*schema.AvatarInfo)
for _, user := range userList { 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 return avatarMapping
} }
@ -114,10 +114,18 @@ func (s *siteInfoCommonService) getAvatarDefaultConfig(ctx context.Context) (str
} }
func (s *siteInfoCommonService) selectedAvatar( 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{} avatarInfo := &schema.AvatarInfo{}
_ = json.Unmarshal([]byte(originalAvatarData), avatarInfo) _ = json.Unmarshal([]byte(originalAvatarData), avatarInfo)
if userStatus == entity.UserStatusDeleted {
return &schema.AvatarInfo{
Type: constant.DefaultAvatar,
}
}
if len(avatarInfo.Type) == 0 && defaultAvatar == constant.AvatarTypeGravatar { if len(avatarInfo.Type) == 0 && defaultAvatar == constant.AvatarTypeGravatar {
avatarInfo.Type = constant.AvatarTypeGravatar avatarInfo.Type = constant.AvatarTypeGravatar
avatarInfo.Gravatar = gravatar.GetAvatarURL(gravatarBaseURL, email) 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/handler"
"github.com/answerdev/answer/internal/base/translator" "github.com/answerdev/answer/internal/base/translator"
"github.com/answerdev/answer/internal/base/validator" "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" "github.com/answerdev/answer/internal/service/export"
questioncommon "github.com/answerdev/answer/internal/service/question_common"
"github.com/google/uuid" "github.com/google/uuid"
"net/mail" "net/mail"
"strings" "strings"
@ -50,6 +53,9 @@ type UserAdminService struct {
userActivity activity.UserActiveActivityRepo userActivity activity.UserActiveActivityRepo
siteInfoCommonService siteinfo_common.SiteInfoCommonService siteInfoCommonService siteinfo_common.SiteInfoCommonService
emailService *export.EmailService emailService *export.EmailService
questionCommonRepo questioncommon.QuestionRepo
answerCommonRepo answercommon.AnswerRepo
commentCommonRepo comment_common.CommentCommonRepo
} }
// NewUserAdminService new user admin service // NewUserAdminService new user admin service
@ -61,6 +67,9 @@ func NewUserAdminService(
userActivity activity.UserActiveActivityRepo, userActivity activity.UserActiveActivityRepo,
siteInfoCommonService siteinfo_common.SiteInfoCommonService, siteInfoCommonService siteinfo_common.SiteInfoCommonService,
emailService *export.EmailService, emailService *export.EmailService,
questionCommonRepo questioncommon.QuestionRepo,
answerCommonRepo answercommon.AnswerRepo,
commentCommonRepo comment_common.CommentCommonRepo,
) *UserAdminService { ) *UserAdminService {
return &UserAdminService{ return &UserAdminService{
userRepo: userRepo, userRepo: userRepo,
@ -70,6 +79,9 @@ func NewUserAdminService(
userActivity: userActivity, userActivity: userActivity,
siteInfoCommonService: siteInfoCommonService, siteInfoCommonService: siteInfoCommonService,
emailService: emailService, emailService: emailService,
questionCommonRepo: questionCommonRepo,
answerCommonRepo: answerCommonRepo,
commentCommonRepo: commentCommonRepo,
} }
} }
@ -111,6 +123,11 @@ func (us *UserAdminService) UpdateUserStatus(ctx context.Context, req *schema.Up
return err 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 user reputation is zero means this user is inactive, so try to activate this user.
if req.IsNormal() && userInfo.Rank == 0 { if req.IsNormal() && userInfo.Rank == 0 {
return us.userActivity.UserActive(ctx, userInfo.ID) return us.userActivity.UserActive(ctx, userInfo.ID)
@ -118,6 +135,19 @@ func (us *UserAdminService) UpdateUserStatus(ctx context.Context, req *schema.Up
return nil 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 // UpdateUserRole update user role
func (us *UserAdminService) UpdateUserRole(ctx context.Context, req *schema.UpdateUserRoleReq) (err error) { func (us *UserAdminService) UpdateUserRole(ctx context.Context, req *schema.UpdateUserRoleReq) (err error) {
// Users cannot modify their roles // Users cannot modify their roles

View File

@ -69,7 +69,7 @@ func (us *UserCommon) GetUserBasicInfoByID(ctx context.Context, ID string) (
return nil, exist, err return nil, exist, err
} }
info := us.FormatUserBasicInfo(ctx, userInfo) 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 return info, exist, nil
} }
@ -79,7 +79,7 @@ func (us *UserCommon) GetUserBasicInfoByUserName(ctx context.Context, username s
return nil, exist, err return nil, exist, err
} }
info := us.FormatUserBasicInfo(ctx, userInfo) 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 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) userBasicInfo.Status = constant.ConvertUserStatus(userInfo.Status, userInfo.MailStatus)
if userBasicInfo.Status == constant.UserDeleted { if userBasicInfo.Status == constant.UserDeleted {
userBasicInfo.Avatar = "" userBasicInfo.Avatar = ""
userBasicInfo.DisplayName = "Anonymous" userBasicInfo.DisplayName = "user" + userInfo.ID
} }
return userBasicInfo return userBasicInfo
} }

View File

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