| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package user_admin |
| |
| import ( |
| "context" |
| "fmt" |
| "net/mail" |
| "strings" |
| "time" |
| "unicode" |
| |
| "github.com/apache/answer/internal/base/constant" |
| "github.com/apache/answer/internal/base/handler" |
| "github.com/apache/answer/internal/base/translator" |
| "github.com/apache/answer/internal/base/validator" |
| answercommon "github.com/apache/answer/internal/service/answer_common" |
| "github.com/apache/answer/internal/service/badge" |
| "github.com/apache/answer/internal/service/comment_common" |
| "github.com/apache/answer/internal/service/export" |
| notificationcommon "github.com/apache/answer/internal/service/notification_common" |
| "github.com/apache/answer/internal/service/plugin_common" |
| questioncommon "github.com/apache/answer/internal/service/question_common" |
| "github.com/apache/answer/pkg/token" |
| |
| "github.com/apache/answer/internal/base/pager" |
| "github.com/apache/answer/internal/base/reason" |
| "github.com/apache/answer/internal/entity" |
| "github.com/apache/answer/internal/schema" |
| "github.com/apache/answer/internal/service/activity" |
| "github.com/apache/answer/internal/service/auth" |
| "github.com/apache/answer/internal/service/role" |
| "github.com/apache/answer/internal/service/siteinfo_common" |
| usercommon "github.com/apache/answer/internal/service/user_common" |
| "github.com/apache/answer/internal/service/user_external_login" |
| "github.com/apache/answer/pkg/checker" |
| "github.com/jinzhu/copier" |
| "github.com/segmentfault/pacman/errors" |
| "github.com/segmentfault/pacman/log" |
| "golang.org/x/crypto/bcrypt" |
| ) |
| |
| // UserAdminRepo user repository |
| type UserAdminRepo interface { |
| UpdateUserStatus(ctx context.Context, userID string, userStatus, mailStatus int, email string, suspendedUntil time.Time) (err error) |
| GetUserInfo(ctx context.Context, userID string) (user *entity.User, exist bool, err error) |
| GetUserInfoByEmail(ctx context.Context, email string) (user *entity.User, exist bool, err error) |
| GetUserPage(ctx context.Context, page, pageSize int, user *entity.User, |
| usernameOrDisplayName string, isStaff bool) (users []*entity.User, total int64, err error) |
| AddUser(ctx context.Context, user *entity.User) (err error) |
| AddUsers(ctx context.Context, users []*entity.User) (err error) |
| UpdateUserPassword(ctx context.Context, userID string, password string) (err error) |
| DeletePermanentlyUsers(ctx context.Context) (err error) |
| GetExpiredSuspendedUsers(ctx context.Context) (users []*entity.User, err error) |
| } |
| |
| // UserAdminService user service |
| type UserAdminService struct { |
| userRepo UserAdminRepo |
| userRoleRelService *role.UserRoleRelService |
| authService *auth.AuthService |
| userCommonService *usercommon.UserCommon |
| userActivity activity.UserActiveActivityRepo |
| siteInfoCommonService siteinfo_common.SiteInfoCommonService |
| emailService *export.EmailService |
| questionCommonRepo questioncommon.QuestionRepo |
| answerCommonRepo answercommon.AnswerRepo |
| commentCommonRepo comment_common.CommentCommonRepo |
| userExternalLoginRepo user_external_login.UserExternalLoginRepo |
| notificationRepo notificationcommon.NotificationRepo |
| pluginUserConfigRepo plugin_common.PluginUserConfigRepo |
| badgeAwardRepo badge.BadgeAwardRepo |
| } |
| |
| // NewUserAdminService new user admin service |
| func NewUserAdminService( |
| userRepo UserAdminRepo, |
| userRoleRelService *role.UserRoleRelService, |
| authService *auth.AuthService, |
| userCommonService *usercommon.UserCommon, |
| userActivity activity.UserActiveActivityRepo, |
| siteInfoCommonService siteinfo_common.SiteInfoCommonService, |
| emailService *export.EmailService, |
| questionCommonRepo questioncommon.QuestionRepo, |
| answerCommonRepo answercommon.AnswerRepo, |
| commentCommonRepo comment_common.CommentCommonRepo, |
| userExternalLoginRepo user_external_login.UserExternalLoginRepo, |
| notificationRepo notificationcommon.NotificationRepo, |
| pluginUserConfigRepo plugin_common.PluginUserConfigRepo, |
| badgeAwardRepo badge.BadgeAwardRepo, |
| ) *UserAdminService { |
| return &UserAdminService{ |
| userRepo: userRepo, |
| userRoleRelService: userRoleRelService, |
| authService: authService, |
| userCommonService: userCommonService, |
| userActivity: userActivity, |
| siteInfoCommonService: siteInfoCommonService, |
| emailService: emailService, |
| questionCommonRepo: questionCommonRepo, |
| answerCommonRepo: answerCommonRepo, |
| commentCommonRepo: commentCommonRepo, |
| userExternalLoginRepo: userExternalLoginRepo, |
| notificationRepo: notificationRepo, |
| pluginUserConfigRepo: pluginUserConfigRepo, |
| badgeAwardRepo: badgeAwardRepo, |
| } |
| } |
| |
| // UpdateUserStatus update user |
| func (us *UserAdminService) UpdateUserStatus(ctx context.Context, req *schema.UpdateUserStatusReq) (err error) { |
| // Admin cannot modify their status |
| if req.UserID == req.LoginUserID { |
| return errors.BadRequest(reason.AdminCannotModifySelfStatus) |
| } |
| userInfo, exist, err := us.userRepo.GetUserInfo(ctx, req.UserID) |
| if err != nil { |
| return |
| } |
| if !exist { |
| return errors.BadRequest(reason.UserNotFound) |
| } |
| // if user status is deleted |
| if userInfo.Status == entity.UserStatusDeleted { |
| return nil |
| } |
| |
| if req.IsInactive() { |
| userInfo.MailStatus = entity.EmailStatusToBeVerified |
| } |
| if req.IsDeleted() { |
| userInfo.Status = entity.UserStatusDeleted |
| userInfo.EMail = fmt.Sprintf("%s.%d", userInfo.EMail, time.Now().Unix()) |
| } |
| if req.IsSuspended() { |
| userInfo.Status = entity.UserStatusSuspended |
| } |
| if req.IsNormal() { |
| userInfo.Status = entity.UserStatusAvailable |
| userInfo.MailStatus = entity.EmailStatusAvailable |
| } |
| |
| suspendedUntil := req.GetSuspendedUntil() |
| err = us.userRepo.UpdateUserStatus(ctx, userInfo.ID, userInfo.Status, userInfo.MailStatus, userInfo.EMail, suspendedUntil) |
| if err != nil { |
| return err |
| } |
| |
| // remove all content that user created, such as question, answer, comment, etc. |
| if req.RemoveAllContent { |
| us.removeAllUserCreatedContent(ctx, userInfo.ID) |
| } |
| |
| if req.IsDeleted() { |
| us.removeAllUserConfiguration(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) |
| } |
| return nil |
| } |
| |
| // removeAllUserConfiguration remove all user configuration |
| func (us *UserAdminService) removeAllUserConfiguration(ctx context.Context, userID string) { |
| err := us.userExternalLoginRepo.DeleteUserExternalLoginByUserID(ctx, userID) |
| if err != nil { |
| log.Errorf("remove all user external login error: %v", err) |
| } |
| err = us.notificationRepo.DeleteNotification(ctx, userID) |
| if err != nil { |
| log.Errorf("remove all user notification error: %v", err) |
| } |
| err = us.notificationRepo.DeleteUserNotificationConfig(ctx, userID) |
| if err != nil { |
| log.Errorf("remove all user notification config error: %v", err) |
| } |
| err = us.pluginUserConfigRepo.DeleteUserPluginConfig(ctx, userID) |
| if err != nil { |
| log.Errorf("remove all user plugin config error: %v", err) |
| } |
| err = us.badgeAwardRepo.DeleteUserBadgeAward(ctx, userID) |
| if err != nil { |
| log.Errorf("remove all user badge award error: %v", err) |
| } |
| } |
| |
| // 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 |
| if req.UserID == req.LoginUserID { |
| return errors.BadRequest(reason.UserCannotUpdateYourRole) |
| } |
| |
| err = us.userRoleRelService.SaveUserRole(ctx, req.UserID, req.RoleID) |
| if err != nil { |
| return err |
| } |
| |
| us.authService.RemoveUserAllTokens(ctx, req.UserID) |
| return |
| } |
| |
| // AddUser add user |
| func (us *UserAdminService) AddUser(ctx context.Context, req *schema.AddUserReq) (err error) { |
| _, has, err := us.userRepo.GetUserInfoByEmail(ctx, req.Email) |
| if err != nil { |
| return err |
| } |
| if has { |
| return errors.BadRequest(reason.EmailDuplicate) |
| } |
| |
| hashPwd, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) |
| if err != nil { |
| return err |
| } |
| |
| userInfo := &entity.User{} |
| userInfo.EMail = req.Email |
| userInfo.DisplayName = req.DisplayName |
| userInfo.Pass = string(hashPwd) |
| |
| userInfo.Username, err = us.userCommonService.MakeUsername(ctx, userInfo.DisplayName) |
| if err != nil { |
| return err |
| } |
| userInfo.MailStatus = entity.EmailStatusAvailable |
| userInfo.Status = entity.UserStatusAvailable |
| userInfo.Rank = 1 |
| |
| err = us.userRepo.AddUser(ctx, userInfo) |
| if err != nil { |
| return err |
| } |
| return |
| } |
| |
| // AddUsers add users |
| func (us *UserAdminService) AddUsers(ctx context.Context, req *schema.AddUsersReq) ( |
| resp []*validator.FormErrorField, err error) { |
| resp, err = req.ParseUsers(ctx) |
| if err != nil { |
| return resp, err |
| } |
| errData := us.checkUserDuplicateInner(ctx, req.Users) |
| if errData != nil { |
| return errData.GetErrField(ctx), errors.BadRequest(reason.RequestFormatError) |
| } |
| users, errData, err := us.formatBulkAddUsers(ctx, req) |
| if err != nil { |
| return resp, err |
| } |
| if errData != nil { |
| return errData.GetErrField(ctx), errors.BadRequest(reason.RequestFormatError) |
| } |
| return nil, us.userRepo.AddUsers(ctx, users) |
| } |
| |
| func (us *UserAdminService) checkUserDuplicateInner(ctx context.Context, users []*schema.AddUserReq) ( |
| errorData *schema.AddUsersErrorData) { |
| lang := handler.GetLangByCtx(ctx) |
| val := validator.GetValidatorByLang(lang) |
| |
| emails := make(map[string]bool) |
| displayNames := make(map[string]bool) |
| for line, user := range users { |
| if errFields, e := val.Check(user); e != nil { |
| errorData = &schema.AddUsersErrorData{} |
| if len(errFields) > 0 { |
| errorData.Field = errFields[0].ErrorField |
| errorData.ExtraMessage = errFields[0].ErrorMsg |
| } |
| errorData.Line = line + 1 |
| errorData.Content = fmt.Sprintf("%s, %s, %s", user.DisplayName, user.Email, user.Password) |
| return errorData |
| } |
| if emails[user.Email] { |
| return &schema.AddUsersErrorData{ |
| Field: "email", |
| Line: line + 1, |
| Content: user.Email, |
| ExtraMessage: translator.Tr(lang, reason.EmailDuplicate), |
| } |
| } |
| if displayNames[user.DisplayName] { |
| return &schema.AddUsersErrorData{ |
| Field: "name", |
| Line: line + 1, |
| Content: user.DisplayName, |
| ExtraMessage: translator.Tr(lang, reason.UsernameDuplicate), |
| } |
| } |
| emails[user.Email] = true |
| displayNames[user.DisplayName] = true |
| } |
| return nil |
| } |
| |
| func (us *UserAdminService) formatBulkAddUsers(ctx context.Context, req *schema.AddUsersReq) ( |
| users []*entity.User, errorData *schema.AddUsersErrorData, err error) { |
| lang := handler.GetLangByCtx(ctx) |
| errorData = &schema.AddUsersErrorData{Line: -1} |
| for line, user := range req.Users { |
| _, has, e := us.userRepo.GetUserInfoByEmail(ctx, user.Email) |
| if e != nil { |
| return nil, nil, e |
| } |
| if has { |
| errorData.Field = "email" |
| errorData.Line = line + 1 |
| errorData.Content = user.Email |
| errorData.ExtraMessage = translator.Tr(lang, reason.EmailDuplicate) |
| return nil, errorData, nil |
| } |
| |
| userInfo := &entity.User{} |
| userInfo.EMail = user.Email |
| userInfo.DisplayName = user.DisplayName |
| hashPwd, _ := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost) |
| userInfo.Pass = string(hashPwd) |
| userInfo.Username, err = us.userCommonService.MakeUsername(ctx, userInfo.DisplayName) |
| if err != nil { |
| errorData.Field = "name" |
| errorData.Line = line + 1 |
| errorData.Content = user.DisplayName |
| errorData.ExtraMessage = translator.Tr(lang, reason.UsernameInvalid) |
| return nil, errorData, nil |
| } |
| userInfo.MailStatus = entity.EmailStatusAvailable |
| userInfo.Status = entity.UserStatusAvailable |
| userInfo.Rank = 1 |
| users = append(users, userInfo) |
| } |
| return users, nil, nil |
| } |
| |
| // UpdateUserPassword update user password |
| func (us *UserAdminService) UpdateUserPassword(ctx context.Context, req *schema.UpdateUserPasswordReq) (err error) { |
| // Users cannot modify their password |
| if req.UserID == req.LoginUserID { |
| return errors.BadRequest(reason.AdminCannotUpdateTheirPassword) |
| } |
| userInfo, exist, err := us.userRepo.GetUserInfo(ctx, req.UserID) |
| if err != nil { |
| return err |
| } |
| if !exist { |
| return errors.BadRequest(reason.UserNotFound) |
| } |
| |
| hashPwd, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) |
| if err != nil { |
| return err |
| } |
| |
| err = us.userRepo.UpdateUserPassword(ctx, userInfo.ID, string(hashPwd)) |
| if err != nil { |
| return err |
| } |
| // logout this user |
| us.authService.RemoveUserAllTokens(ctx, req.UserID) |
| return |
| } |
| |
| // EditUserProfile edit user profile |
| func (us *UserAdminService) EditUserProfile(ctx context.Context, req *schema.EditUserProfileReq) ( |
| errFields []*validator.FormErrorField, err error) { |
| if req.UserID == req.LoginUserID { |
| return nil, errors.BadRequest(reason.AdminCannotEditTheirProfile) |
| } |
| _, exist, err := us.userRepo.GetUserInfo(ctx, req.UserID) |
| if err != nil { |
| return nil, err |
| } |
| if !exist { |
| return nil, errors.BadRequest(reason.UserNotFound) |
| } |
| |
| if checker.IsInvalidUsername(req.Username) || checker.IsUsersIgnorePath(req.Username) { |
| return append(errFields, &validator.FormErrorField{ |
| ErrorField: "username", |
| ErrorMsg: reason.UsernameInvalid, |
| }), errors.BadRequest(reason.UsernameInvalid) |
| } |
| |
| userInfo, exist, err := us.userCommonService.GetByUsername(ctx, req.Username) |
| if err != nil { |
| return nil, err |
| } |
| if exist && userInfo.ID != req.UserID { |
| return append(errFields, &validator.FormErrorField{ |
| ErrorField: "username", |
| ErrorMsg: reason.UsernameDuplicate, |
| }), errors.BadRequest(reason.UsernameDuplicate) |
| } |
| |
| userInfo, exist, err = us.userCommonService.GetByEmail(ctx, req.Email) |
| if err != nil { |
| return nil, err |
| } |
| if exist && userInfo.ID != req.UserID { |
| return append(errFields, &validator.FormErrorField{ |
| ErrorField: "email", |
| ErrorMsg: reason.EmailDuplicate, |
| }), errors.BadRequest(reason.EmailDuplicate) |
| } |
| |
| user := &entity.User{} |
| user.ID = req.UserID |
| user.DisplayName = req.DisplayName |
| user.Username = req.Username |
| user.EMail = req.Email |
| user.MailStatus = entity.EmailStatusAvailable |
| err = us.userCommonService.UpdateUserProfile(ctx, user) |
| if err != nil { |
| return nil, err |
| } |
| return |
| } |
| |
| // GetUserInfo get user one |
| func (us *UserAdminService) GetUserInfo(ctx context.Context, userID string) (resp *schema.GetUserInfoResp, err error) { |
| user, exist, err := us.userRepo.GetUserInfo(ctx, userID) |
| if err != nil { |
| return |
| } |
| if !exist { |
| return nil, errors.BadRequest(reason.UserNotFound) |
| } |
| |
| resp = &schema.GetUserInfoResp{} |
| _ = copier.Copy(resp, user) |
| return resp, nil |
| } |
| |
| // GetUserPage get user list page |
| func (us *UserAdminService) GetUserPage(ctx context.Context, req *schema.GetUserPageReq) (pageModel *pager.PageModel, err error) { |
| user := &entity.User{} |
| _ = copier.Copy(user, req) |
| |
| switch { |
| case req.IsInactive(): |
| user.MailStatus = entity.EmailStatusToBeVerified |
| user.Status = entity.UserStatusAvailable |
| case req.IsSuspended(): |
| user.Status = entity.UserStatusSuspended |
| case req.IsDeleted(): |
| user.Status = entity.UserStatusDeleted |
| default: |
| user.MailStatus = entity.EmailStatusAvailable |
| user.Status = entity.UserStatusAvailable |
| } |
| |
| if len(req.Query) > 0 { |
| if email, e := mail.ParseAddress(req.Query); e == nil { |
| user.EMail = email.Address |
| req.Query = "" |
| } else if after, ok := strings.CutPrefix(req.Query, "user:"); ok { |
| id := strings.TrimSpace(after) |
| idSearch := true |
| for _, r := range id { |
| if !unicode.IsDigit(r) { |
| idSearch = false |
| break |
| } |
| } |
| if idSearch { |
| user.ID = id |
| req.Query = "" |
| } else { |
| req.Query = id |
| } |
| } |
| } |
| |
| users, total, err := us.userRepo.GetUserPage(ctx, req.Page, req.PageSize, user, req.Query, req.Staff) |
| if err != nil { |
| return |
| } |
| avatarMapping := us.siteInfoCommonService.FormatListAvatar(ctx, users) |
| |
| resp := make([]*schema.GetUserPageResp, 0) |
| for _, u := range users { |
| t := &schema.GetUserPageResp{ |
| UserID: u.ID, |
| CreatedAt: u.CreatedAt.Unix(), |
| Username: u.Username, |
| EMail: u.EMail, |
| Rank: u.Rank, |
| DisplayName: u.DisplayName, |
| Avatar: avatarMapping[u.ID].GetURL(), |
| } |
| switch { |
| case u.Status == entity.UserStatusDeleted: |
| t.Status = constant.UserDeleted |
| t.DeletedAt = u.DeletedAt.Unix() |
| case u.Status == entity.UserStatusSuspended: |
| t.Status = constant.UserSuspended |
| t.SuspendedAt = u.SuspendedAt.Unix() |
| if !u.SuspendedUntil.IsZero() { |
| t.SuspendedUntil = u.SuspendedUntil.Unix() |
| } |
| case u.MailStatus == entity.EmailStatusToBeVerified: |
| t.Status = constant.UserInactive |
| default: |
| t.Status = constant.UserNormal |
| } |
| resp = append(resp, t) |
| } |
| us.setUserRoleInfo(ctx, resp) |
| return pager.NewPageModel(total, resp), nil |
| } |
| |
| func (us *UserAdminService) setUserRoleInfo(ctx context.Context, resp []*schema.GetUserPageResp) { |
| var userIDs []string |
| for _, u := range resp { |
| userIDs = append(userIDs, u.UserID) |
| } |
| |
| userRoleMapping, err := us.userRoleRelService.GetUserRoleMapping(ctx, userIDs) |
| if err != nil { |
| log.Error(err) |
| return |
| } |
| |
| for _, u := range resp { |
| r := userRoleMapping[u.UserID] |
| if r == nil { |
| continue |
| } |
| u.RoleID = r.ID |
| u.RoleName = r.Name |
| } |
| } |
| |
| func (us *UserAdminService) GetUserActivation(ctx context.Context, req *schema.GetUserActivationReq) ( |
| resp *schema.GetUserActivationResp, err error) { |
| userInfo, exist, err := us.userRepo.GetUserInfo(ctx, req.UserID) |
| if err != nil { |
| return nil, err |
| } |
| if !exist { |
| return nil, errors.BadRequest(reason.UserNotFound) |
| } |
| |
| general, err := us.siteInfoCommonService.GetSiteGeneral(ctx) |
| if err != nil { |
| return nil, err |
| } |
| |
| data := &schema.EmailCodeContent{ |
| Email: userInfo.EMail, |
| UserID: userInfo.ID, |
| } |
| code := token.GenerateToken() |
| us.emailService.SaveCode(ctx, userInfo.ID, code, data.ToJSONString()) |
| resp = &schema.GetUserActivationResp{ |
| ActivationURL: fmt.Sprintf("%s/users/account-activation?code=%s", general.SiteUrl, code), |
| } |
| return resp, nil |
| } |
| |
| // SendUserActivation send user activation email |
| func (us *UserAdminService) SendUserActivation(ctx context.Context, req *schema.SendUserActivationReq) (err error) { |
| userInfo, exist, err := us.userRepo.GetUserInfo(ctx, req.UserID) |
| if err != nil { |
| return err |
| } |
| if !exist { |
| return errors.BadRequest(reason.UserNotFound) |
| } |
| |
| general, err := us.siteInfoCommonService.GetSiteGeneral(ctx) |
| if err != nil { |
| return err |
| } |
| |
| data := &schema.EmailCodeContent{ |
| Email: userInfo.EMail, |
| UserID: userInfo.ID, |
| } |
| code := token.GenerateToken() |
| verifyEmailURL := fmt.Sprintf("%s/users/account-activation?code=%s", general.SiteUrl, code) |
| title, body, err := us.emailService.RegisterTemplate(ctx, verifyEmailURL) |
| if err != nil { |
| return err |
| } |
| go us.emailService.SendAndSaveCode(ctx, userInfo.ID, userInfo.EMail, title, body, code, data.ToJSONString()) |
| return nil |
| } |
| |
| func (us *UserAdminService) DeletePermanently(ctx context.Context, req *schema.DeletePermanentlyReq) (err error) { |
| switch req.Type { |
| case constant.DeletePermanentlyUsers: |
| return us.userRepo.DeletePermanentlyUsers(ctx) |
| case constant.DeletePermanentlyQuestions: |
| return us.questionCommonRepo.DeletePermanentlyQuestions(ctx) |
| case constant.DeletePermanentlyAnswers: |
| return us.answerCommonRepo.DeletePermanentlyAnswers(ctx) |
| } |
| |
| return errors.BadRequest(reason.RequestFormatError) |
| } |
| |
| // CheckAndUnsuspendExpiredUsers checks for users whose suspension has expired and restores them to normal status |
| func (us *UserAdminService) CheckAndUnsuspendExpiredUsers(ctx context.Context) error { |
| // Find all suspended users whose suspension time has expired |
| expiredUsers, err := us.userRepo.GetExpiredSuspendedUsers(ctx) |
| if err != nil { |
| return err |
| } |
| |
| now := time.Now() |
| for _, user := range expiredUsers { |
| // Check if suspension has expired (not permanent and time has passed) |
| if user.Status == entity.UserStatusSuspended && |
| !user.SuspendedUntil.IsZero() && |
| user.SuspendedUntil.Before(now) { |
| log.Infof("Unsuspending user %s (ID: %s) - suspension expired at %v", |
| user.Username, user.ID, user.SuspendedUntil) |
| |
| // Update user status to normal |
| err = us.userRepo.UpdateUserStatus(ctx, user.ID, entity.UserStatusAvailable, |
| entity.EmailStatusAvailable, user.EMail, time.Time{}) |
| if err != nil { |
| log.Errorf("Failed to unsuspend user %s (ID: %s): %v", |
| user.Username, user.ID, err) |
| continue |
| } |
| } |
| } |
| |
| return nil |
| } |