262 lines
9.4 KiB
Go
262 lines
9.4 KiB
Go
/*
|
|
* 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 usercommon
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/apache/answer/internal/base/constant"
|
|
"github.com/apache/answer/pkg/converter"
|
|
|
|
"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/auth"
|
|
"github.com/apache/answer/internal/service/role"
|
|
"github.com/apache/answer/internal/service/siteinfo_common"
|
|
"github.com/apache/answer/pkg/checker"
|
|
"github.com/apache/answer/pkg/random"
|
|
"github.com/mozillazg/go-pinyin"
|
|
"github.com/segmentfault/pacman/errors"
|
|
"github.com/segmentfault/pacman/log"
|
|
)
|
|
|
|
type UserRepo interface {
|
|
AddUser(ctx context.Context, user *entity.User) (err error)
|
|
IncreaseAnswerCount(ctx context.Context, userID string, amount int) (err error)
|
|
IncreaseQuestionCount(ctx context.Context, userID string, amount int) (err error)
|
|
UpdateQuestionCount(ctx context.Context, userID string, count int64) (err error)
|
|
UpdateAnswerCount(ctx context.Context, userID string, count int) (err error)
|
|
UpdateLastLoginDate(ctx context.Context, userID string) (err error)
|
|
UpdateEmailStatus(ctx context.Context, userID string, emailStatus int) error
|
|
UpdateNoticeStatus(ctx context.Context, userID string, noticeStatus int) error
|
|
UpdateEmail(ctx context.Context, userID, email string) error
|
|
UpdateUserInterface(ctx context.Context, userID, language, colorSchema string) (err error)
|
|
UpdatePass(ctx context.Context, userID, pass string) error
|
|
UpdateInfo(ctx context.Context, userInfo *entity.User) (err error)
|
|
UpdateUserProfile(ctx context.Context, userInfo *entity.User) (err error)
|
|
GetByUserID(ctx context.Context, userID string) (userInfo *entity.User, exist bool, err error)
|
|
BatchGetByID(ctx context.Context, ids []string) ([]*entity.User, error)
|
|
GetByUsername(ctx context.Context, username string) (userInfo *entity.User, exist bool, err error)
|
|
GetByUsernames(ctx context.Context, usernames []string) ([]*entity.User, error)
|
|
GetByEmail(ctx context.Context, email string) (userInfo *entity.User, exist bool, err error)
|
|
GetUserCount(ctx context.Context) (count int64, err error)
|
|
SearchUserListByName(ctx context.Context, name string, limit int, onlyStaff bool) (userList []*entity.User, err error)
|
|
IsAvatarFileUsed(ctx context.Context, filePath string) (bool, error)
|
|
}
|
|
|
|
// UserCommon user service
|
|
type UserCommon struct {
|
|
userRepo UserRepo
|
|
userRoleService *role.UserRoleRelService
|
|
authService *auth.AuthService
|
|
siteInfoCommonService siteinfo_common.SiteInfoCommonService
|
|
}
|
|
|
|
func NewUserCommon(
|
|
userRepo UserRepo,
|
|
userRoleService *role.UserRoleRelService,
|
|
authService *auth.AuthService,
|
|
siteInfoCommonService siteinfo_common.SiteInfoCommonService,
|
|
) *UserCommon {
|
|
return &UserCommon{
|
|
userRepo: userRepo,
|
|
userRoleService: userRoleService,
|
|
authService: authService,
|
|
siteInfoCommonService: siteInfoCommonService,
|
|
}
|
|
}
|
|
|
|
func (us *UserCommon) GetUserBasicInfoByID(ctx context.Context, id string) (
|
|
userBasicInfo *schema.UserBasicInfo, exist bool, err error) {
|
|
userInfo, exist, err := us.userRepo.GetByUserID(ctx, id)
|
|
if err != nil {
|
|
return nil, exist, err
|
|
}
|
|
info := us.FormatUserBasicInfo(ctx, userInfo)
|
|
info.Avatar = us.siteInfoCommonService.FormatAvatar(ctx, userInfo.Avatar, userInfo.EMail, userInfo.Status).GetURL()
|
|
return info, exist, nil
|
|
}
|
|
|
|
func (us *UserCommon) GetUserBasicInfoByUserName(ctx context.Context, username string) (*schema.UserBasicInfo, bool, error) {
|
|
userInfo, exist, err := us.userRepo.GetByUsername(ctx, username)
|
|
if err != nil {
|
|
return nil, exist, err
|
|
}
|
|
info := us.FormatUserBasicInfo(ctx, userInfo)
|
|
info.Avatar = us.siteInfoCommonService.FormatAvatar(ctx, userInfo.Avatar, userInfo.EMail, userInfo.Status).GetURL()
|
|
return info, exist, nil
|
|
}
|
|
|
|
func (us *UserCommon) BatchGetUserBasicInfoByUserNames(ctx context.Context, usernames []string) (map[string]*schema.UserBasicInfo, error) {
|
|
infomap := make(map[string]*schema.UserBasicInfo)
|
|
list, err := us.userRepo.GetByUsernames(ctx, usernames)
|
|
if err != nil {
|
|
return infomap, err
|
|
}
|
|
avatarMapping := us.siteInfoCommonService.FormatListAvatar(ctx, list)
|
|
for _, user := range list {
|
|
info := us.FormatUserBasicInfo(ctx, user)
|
|
info.Avatar = avatarMapping[user.ID].GetURL()
|
|
infomap[user.Username] = info
|
|
}
|
|
return infomap, nil
|
|
}
|
|
|
|
func (us *UserCommon) GetByEmail(ctx context.Context, email string) (userInfo *entity.User, exist bool, err error) {
|
|
return us.userRepo.GetByEmail(ctx, email)
|
|
}
|
|
|
|
func (us *UserCommon) GetByUsername(ctx context.Context, username string) (userInfo *entity.User, exist bool, err error) {
|
|
return us.userRepo.GetByUsername(ctx, username)
|
|
}
|
|
|
|
func (us *UserCommon) UpdateUserProfile(ctx context.Context, userInfo *entity.User) (err error) {
|
|
return us.userRepo.UpdateUserProfile(ctx, userInfo)
|
|
}
|
|
|
|
func (us *UserCommon) UpdateAnswerCount(ctx context.Context, userID string, num int) error {
|
|
return us.userRepo.UpdateAnswerCount(ctx, userID, num)
|
|
}
|
|
|
|
func (us *UserCommon) UpdateQuestionCount(ctx context.Context, userID string, num int64) error {
|
|
return us.userRepo.UpdateQuestionCount(ctx, userID, num)
|
|
}
|
|
|
|
func (us *UserCommon) BatchUserBasicInfoByID(ctx context.Context, userIDs []string) (map[string]*schema.UserBasicInfo, error) {
|
|
userIDs = checker.FilterEmptyString(userIDs)
|
|
userMap := make(map[string]*schema.UserBasicInfo)
|
|
if len(userIDs) == 0 {
|
|
return userMap, nil
|
|
}
|
|
userList, err := us.userRepo.BatchGetByID(ctx, userIDs)
|
|
if err != nil {
|
|
return userMap, err
|
|
}
|
|
avatarMapping := us.siteInfoCommonService.FormatListAvatar(ctx, userList)
|
|
for _, user := range userList {
|
|
info := us.FormatUserBasicInfo(ctx, user)
|
|
info.Avatar = avatarMapping[user.ID].GetURL()
|
|
userMap[user.ID] = info
|
|
}
|
|
for _, id := range userIDs {
|
|
if _, ok := userMap[id]; !ok {
|
|
userMap[id] = &schema.UserBasicInfo{
|
|
ID: id,
|
|
DisplayName: "user" + converter.DeleteUserDisplay(id),
|
|
Status: constant.UserDeleted,
|
|
}
|
|
}
|
|
}
|
|
return userMap, nil
|
|
}
|
|
|
|
// FormatUserBasicInfo format user basic info
|
|
func (us *UserCommon) FormatUserBasicInfo(ctx context.Context, userInfo *entity.User) *schema.UserBasicInfo {
|
|
userBasicInfo := &schema.UserBasicInfo{}
|
|
userBasicInfo.ID = userInfo.ID
|
|
userBasicInfo.Username = userInfo.Username
|
|
userBasicInfo.Rank = userInfo.Rank
|
|
userBasicInfo.DisplayName = userInfo.DisplayName
|
|
userBasicInfo.Website = userInfo.Website
|
|
userBasicInfo.Location = userInfo.Location
|
|
userBasicInfo.Language = userInfo.Language
|
|
userBasicInfo.Status = constant.ConvertUserStatus(userInfo.Status, userInfo.MailStatus)
|
|
if !userInfo.SuspendedUntil.IsZero() {
|
|
userBasicInfo.SuspendedUntil = userInfo.SuspendedUntil.Unix()
|
|
}
|
|
if userBasicInfo.Status == constant.UserDeleted {
|
|
userBasicInfo.Avatar = ""
|
|
userBasicInfo.DisplayName = "user" + converter.DeleteUserDisplay(userInfo.ID)
|
|
}
|
|
return userBasicInfo
|
|
}
|
|
|
|
// MakeUsername
|
|
// Generate a unique Username based on the displayName
|
|
func (us *UserCommon) MakeUsername(ctx context.Context, displayName string) (username string, err error) {
|
|
// Chinese processing
|
|
if has := checker.IsChinese(displayName); has {
|
|
displayName = strings.Join(pinyin.LazyConvert(displayName, nil), "")
|
|
}
|
|
|
|
username = strings.ReplaceAll(displayName, " ", "-")
|
|
username = strings.ToLower(username)
|
|
suffix := ""
|
|
|
|
if checker.IsInvalidUsername(username) {
|
|
return "", errors.BadRequest(reason.UsernameInvalid)
|
|
}
|
|
|
|
if checker.IsReservedUsername(username) {
|
|
return "", errors.BadRequest(reason.UsernameInvalid)
|
|
}
|
|
|
|
for {
|
|
_, has, err := us.userRepo.GetByUsername(ctx, username+suffix)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if !has {
|
|
break
|
|
}
|
|
suffix = random.UsernameSuffix()
|
|
}
|
|
return username + suffix, nil
|
|
}
|
|
|
|
func (us *UserCommon) CacheLoginUserInfo(ctx context.Context, userID string, userStatus, emailStatus int, externalID string) (
|
|
accessToken string, userCacheInfo *entity.UserCacheInfo, err error) {
|
|
roleID, err := us.userRoleService.GetUserRole(ctx, userID)
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
|
|
userCacheInfo = &entity.UserCacheInfo{
|
|
UserID: userID,
|
|
EmailStatus: emailStatus,
|
|
UserStatus: userStatus,
|
|
RoleID: roleID,
|
|
ExternalID: externalID,
|
|
}
|
|
|
|
accessToken, _, err = us.authService.SetUserCacheInfo(ctx, userCacheInfo)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
if userCacheInfo.RoleID == role.RoleAdminID {
|
|
if err = us.authService.SetAdminUserCacheInfo(ctx, accessToken, userCacheInfo); err != nil {
|
|
return "", nil, err
|
|
}
|
|
}
|
|
return accessToken, userCacheInfo, nil
|
|
}
|
|
|
|
func (us *UserCommon) IsAvatarFileUsed(ctx context.Context, filePath string) bool {
|
|
used, err := us.userRepo.IsAvatarFileUsed(ctx, filePath)
|
|
if err != nil {
|
|
log.Errorf("error checking if branding file is used: %v", err)
|
|
// will try again with the next clean up
|
|
return true
|
|
}
|
|
return used
|
|
}
|