answer/internal/service/user_admin/user_backyard.go

453 lines
13 KiB
Go
Raw Normal View History

2022-12-21 16:55:16 +08:00
package user_admin
2022-09-27 17:59:05 +08:00
import (
"context"
"fmt"
2023-08-04 15:47:50 +08:00
"github.com/answerdev/answer/internal/base/constant"
2023-08-14 12:13:28 +08:00
"github.com/answerdev/answer/internal/base/handler"
2023-08-14 15:46:13 +08:00
"github.com/answerdev/answer/internal/base/translator"
2023-08-14 12:13:28 +08:00
"github.com/answerdev/answer/internal/base/validator"
"github.com/answerdev/answer/internal/service/export"
"github.com/google/uuid"
2022-11-29 15:10:57 +08:00
"net/mail"
"strings"
2022-09-27 17:59:05 +08:00
"time"
2022-11-29 15:10:57 +08:00
"unicode"
2022-09-27 17:59:05 +08:00
"github.com/answerdev/answer/internal/base/pager"
"github.com/answerdev/answer/internal/base/reason"
"github.com/answerdev/answer/internal/entity"
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/internal/service/activity"
"github.com/answerdev/answer/internal/service/auth"
2022-11-29 15:10:57 +08:00
"github.com/answerdev/answer/internal/service/role"
"github.com/answerdev/answer/internal/service/siteinfo_common"
usercommon "github.com/answerdev/answer/internal/service/user_common"
2022-09-27 17:59:05 +08:00
"github.com/jinzhu/copier"
"github.com/segmentfault/pacman/errors"
2022-11-29 15:10:57 +08:00
"github.com/segmentfault/pacman/log"
"golang.org/x/crypto/bcrypt"
2022-09-27 17:59:05 +08:00
)
2022-12-21 16:55:16 +08:00
// UserAdminRepo user repository
type UserAdminRepo interface {
2022-09-27 17:59:05 +08:00
UpdateUserStatus(ctx context.Context, userID string, userStatus, mailStatus int, email string) (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)
2022-11-29 15:10:57 +08:00
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)
2023-08-14 12:13:28 +08:00
AddUsers(ctx context.Context, users []*entity.User) (err error)
UpdateUserPassword(ctx context.Context, userID string, password string) (err error)
2022-09-27 17:59:05 +08:00
}
2022-12-21 16:55:16 +08:00
// 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
2022-09-27 17:59:05 +08:00
}
2022-12-21 16:55:16 +08:00
// NewUserAdminService new user admin service
func NewUserAdminService(
userRepo UserAdminRepo,
2022-11-29 15:10:57 +08:00
userRoleRelService *role.UserRoleRelService,
authService *auth.AuthService,
userCommonService *usercommon.UserCommon,
userActivity activity.UserActiveActivityRepo,
siteInfoCommonService siteinfo_common.SiteInfoCommonService,
emailService *export.EmailService,
2022-12-21 16:55:16 +08:00
) *UserAdminService {
return &UserAdminService{
userRepo: userRepo,
userRoleRelService: userRoleRelService,
authService: authService,
userCommonService: userCommonService,
userActivity: userActivity,
siteInfoCommonService: siteInfoCommonService,
emailService: emailService,
2022-09-27 17:59:05 +08:00
}
}
// UpdateUserStatus update user
2022-12-21 16:55:16 +08:00
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)
}
2022-09-27 17:59:05 +08:00
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().UnixNano())
}
if req.IsSuspended() {
userInfo.Status = entity.UserStatusSuspended
}
if req.IsNormal() {
userInfo.Status = entity.UserStatusAvailable
userInfo.MailStatus = entity.EmailStatusAvailable
}
err = us.userRepo.UpdateUserStatus(ctx, userInfo.ID, userInfo.Status, userInfo.MailStatus, userInfo.EMail)
if err != nil {
return err
}
// 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
2022-09-27 17:59:05 +08:00
}
2022-11-29 15:10:57 +08:00
// UpdateUserRole update user role
2022-12-21 16:55:16 +08:00
func (us *UserAdminService) UpdateUserRole(ctx context.Context, req *schema.UpdateUserRoleReq) (err error) {
2022-11-29 15:10:57 +08:00
// Users cannot modify their roles
if req.UserID == req.LoginUserID {
return errors.BadRequest(reason.UserCannotUpdateYourRole)
2022-11-29 15:10:57 +08:00
}
err = us.userRoleRelService.SaveUserRole(ctx, req.UserID, req.RoleID)
if err != nil {
return err
}
us.authService.RemoveUserAllTokens(ctx, req.UserID)
return
2022-11-29 15:10:57 +08:00
}
// AddUser add user
2022-12-21 16:55:16 +08:00
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
}
2023-08-14 12:13:28 +08:00
// AddUsers add users
2023-08-14 15:46:13 +08:00
func (us *UserAdminService) AddUsers(ctx context.Context, req *schema.AddUsersReq) (
resp []*validator.FormErrorField, err error) {
resp, err = req.ParseUsers(ctx)
2023-08-14 12:13:28 +08:00
if err != nil {
2023-08-14 15:46:13 +08:00
return resp, err
2023-08-14 12:13:28 +08:00
}
2023-08-14 15:46:13 +08:00
users, resp, err := us.formatBulkAddUsers(ctx, req)
2023-08-14 12:13:28 +08:00
if err != nil {
2023-08-14 15:46:13 +08:00
return resp, err
2023-08-14 12:13:28 +08:00
}
2023-08-14 15:46:13 +08:00
err = us.userRepo.AddUsers(ctx, users)
return nil, err
2023-08-14 12:13:28 +08:00
}
func (us *UserAdminService) formatBulkAddUsers(ctx context.Context, req *schema.AddUsersReq) (
2023-08-14 15:46:13 +08:00
users []*entity.User, errFields []*validator.FormErrorField, err error) {
lang := handler.GetLangByCtx(ctx)
val := validator.GetValidatorByLang(lang)
errorData := &schema.AddUsersErrorData{Line: -1}
2023-08-14 16:21:53 +08:00
existEmails := make(map[string]bool)
existDisplayNames := make(map[string]bool)
2023-08-14 15:46:13 +08:00
for line, user := range req.Users {
2023-08-14 16:21:53 +08:00
if existEmails[user.Email] {
errorData.Field = "email"
errorData.Line = line + 1
errorData.Content = user.Email
errorData.ExtraMessage = translator.Tr(lang, reason.EmailDuplicate)
break
}
if existDisplayNames[user.DisplayName] {
errorData.Field = "displayName"
errorData.Line = line + 1
errorData.Content = user.DisplayName
errorData.ExtraMessage = translator.Tr(lang, reason.UsernameDuplicate)
break
}
2023-08-14 15:46:13 +08:00
if fields, e := val.Check(user); e != nil {
errorData.SetErrField(fields)
errorData.Line = line + 1
errorData.Content = fmt.Sprintf("%s, %s", user.DisplayName, user.Email)
break
2023-08-14 12:13:28 +08:00
}
2023-08-14 15:46:13 +08:00
_, has, e := us.userRepo.GetUserInfoByEmail(ctx, user.Email)
if e != nil {
return nil, nil, e
2023-08-14 12:13:28 +08:00
}
if has {
2023-08-14 15:46:13 +08:00
errorData.Field = "email"
errorData.Line = line + 1
errorData.Content = user.Email
errorData.ExtraMessage = translator.Tr(lang, reason.EmailDuplicate)
break
2023-08-14 12:13:28 +08:00
}
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 {
2023-08-14 15:46:13 +08:00
errorData.Field = "display_name"
errorData.Line = line + 1
errorData.Content = user.DisplayName
errorData.ExtraMessage = translator.Tr(lang, reason.UsernameInvalid)
break
2023-08-14 12:13:28 +08:00
}
userInfo.MailStatus = entity.EmailStatusAvailable
userInfo.Status = entity.UserStatusAvailable
userInfo.Rank = 1
users = append(users, userInfo)
2023-08-14 16:21:53 +08:00
existEmails[user.Email] = true
existDisplayNames[user.DisplayName] = true
2023-08-14 12:13:28 +08:00
}
2023-08-14 15:46:13 +08:00
if errorData.Line != -1 {
errFields = append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "users",
ErrorMsg: translator.TrWithData(handler.GetLangByCtx(ctx), reason.AddBulkUsersFormatError, errorData),
})
return nil, errFields, errors.BadRequest(reason.RequestFormatError)
}
return users, nil, nil
2023-08-14 12:13:28 +08:00
}
// UpdateUserPassword update user password
2022-12-21 16:55:16 +08:00
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
}
2022-09-27 17:59:05 +08:00
// GetUserInfo get user one
2022-12-21 16:55:16 +08:00
func (us *UserAdminService) GetUserInfo(ctx context.Context, userID string) (resp *schema.GetUserInfoResp, err error) {
2022-09-27 17:59:05 +08:00
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
2022-12-21 16:55:16 +08:00
func (us *UserAdminService) GetUserPage(ctx context.Context, req *schema.GetUserPageReq) (pageModel *pager.PageModel, err error) {
2022-09-27 17:59:05 +08:00
user := &entity.User{}
_ = copier.Copy(user, req)
if req.IsInactive() {
user.MailStatus = entity.EmailStatusToBeVerified
user.Status = entity.UserStatusAvailable
} else if req.IsSuspended() {
user.Status = entity.UserStatusSuspended
} else if req.IsDeleted() {
user.Status = entity.UserStatusDeleted
}
2022-11-29 15:10:57 +08:00
if len(req.Query) > 0 {
if email, e := mail.ParseAddress(req.Query); e == nil {
user.EMail = email.Address
req.Query = ""
} else if strings.HasPrefix(req.Query, "user:") {
id := strings.TrimSpace(strings.TrimPrefix(req.Query, "user:"))
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)
2022-09-27 17:59:05 +08:00
if err != nil {
return
}
avatarMapping := us.siteInfoCommonService.FormatListAvatar(ctx, users)
2022-09-27 17:59:05 +08:00
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(),
2022-09-27 17:59:05 +08:00
}
if u.Status == entity.UserStatusDeleted {
2023-08-04 15:47:50 +08:00
t.Status = constant.UserDeleted
2022-09-27 17:59:05 +08:00
t.DeletedAt = u.DeletedAt.Unix()
} else if u.Status == entity.UserStatusSuspended {
2023-08-04 15:47:50 +08:00
t.Status = constant.UserSuspended
2022-09-27 17:59:05 +08:00
t.SuspendedAt = u.SuspendedAt.Unix()
} else if u.MailStatus == entity.EmailStatusToBeVerified {
2023-08-04 15:47:50 +08:00
t.Status = constant.UserInactive
2022-09-27 17:59:05 +08:00
} else {
2023-08-04 15:47:50 +08:00
t.Status = constant.UserNormal
2022-09-27 17:59:05 +08:00
}
resp = append(resp, t)
}
2022-11-29 15:10:57 +08:00
us.setUserRoleInfo(ctx, resp)
return pager.NewPageModel(total, resp), nil
2022-09-27 17:59:05 +08:00
}
2022-11-29 15:10:57 +08:00
2022-12-21 16:55:16 +08:00
func (us *UserAdminService) setUserRoleInfo(ctx context.Context, resp []*schema.GetUserPageResp) {
2022-11-29 15:10:57 +08:00
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) {
user, 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: user.EMail,
UserID: user.ID,
}
code := uuid.NewString()
us.emailService.SaveCode(ctx, 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) {
user, 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: user.EMail,
UserID: user.ID,
}
code := uuid.NewString()
us.emailService.SaveCode(ctx, code, data.ToJSONString())
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, user.EMail, title, body, code, data.ToJSONString())
return nil
}