chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,682 @@
|
||||
/*
|
||||
* 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/apikey"
|
||||
"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
|
||||
apiKeyRepo apikey.APIKeyRepo
|
||||
}
|
||||
|
||||
// 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,
|
||||
apiKeyRepo apikey.APIKeyRepo,
|
||||
) *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,
|
||||
apiKeyRepo: apiKeyRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
if req.IsInactive() || req.IsSuspended() || req.IsDeleted() {
|
||||
if err := us.revokeUserAPIKeys(ctx, userInfo.ID); 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
|
||||
}
|
||||
if err := us.revokeUserAPIKeys(ctx, req.UserID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
us.authService.RemoveUserAllTokens(ctx, req.UserID)
|
||||
return
|
||||
}
|
||||
|
||||
func (us *UserAdminService) revokeUserAPIKeys(ctx context.Context, userID string) error {
|
||||
return us.apiKeyRepo.DeleteAPIKeysByUserID(ctx, userID)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user