mirror of
				https://github.com/cloudreve/cloudreve.git
				synced 2025-10-31 08:39:10 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			255 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			255 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package admin
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"strconv"
 | |
| 
 | |
| 	"github.com/cloudreve/Cloudreve/v4/application/dependency"
 | |
| 	"github.com/cloudreve/Cloudreve/v4/ent"
 | |
| 	"github.com/cloudreve/Cloudreve/v4/ent/user"
 | |
| 	"github.com/cloudreve/Cloudreve/v4/inventory"
 | |
| 	"github.com/cloudreve/Cloudreve/v4/inventory/types"
 | |
| 	"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/manager"
 | |
| 	"github.com/cloudreve/Cloudreve/v4/pkg/hashid"
 | |
| 	"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
 | |
| 	"github.com/gin-gonic/gin"
 | |
| 	"github.com/samber/lo"
 | |
| )
 | |
| 
 | |
| // AddUserService 用户添加服务
 | |
| type AddUserService struct {
 | |
| 	//User     model.User `json:"User" binding:"required"`
 | |
| 	Password string `json:"password"`
 | |
| }
 | |
| 
 | |
| // UserService 用户ID服务
 | |
| type UserService struct {
 | |
| 	ID uint `uri:"id" json:"id" binding:"required"`
 | |
| }
 | |
| 
 | |
| // UserBatchService 用户批量操作服务
 | |
| type UserBatchService struct {
 | |
| 	ID []uint `json:"id" binding:"min=1"`
 | |
| }
 | |
| 
 | |
| const (
 | |
| 	userStatusCondition = "user_status"
 | |
| 	userGroupCondition  = "user_group"
 | |
| 	userNickCondition   = "user_nick"
 | |
| 	userEmailCondition  = "user_email"
 | |
| )
 | |
| 
 | |
| func (service *AdminListService) Users(c *gin.Context) (*ListUserResponse, error) {
 | |
| 	dep := dependency.FromContext(c)
 | |
| 	hasher := dep.HashIDEncoder()
 | |
| 	userClient := dep.UserClient()
 | |
| 
 | |
| 	ctx := context.WithValue(c, inventory.LoadUserGroup{}, true)
 | |
| 	ctx = context.WithValue(ctx, inventory.LoadUserPasskey{}, true)
 | |
| 
 | |
| 	var (
 | |
| 		err     error
 | |
| 		groupID int
 | |
| 	)
 | |
| 	if service.Conditions[userGroupCondition] != "" {
 | |
| 		groupID, err = strconv.Atoi(service.Conditions[userGroupCondition])
 | |
| 		if err != nil {
 | |
| 			return nil, serializer.NewError(serializer.CodeParamErr, "Invalid group ID", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	res, err := userClient.ListUsers(ctx, &inventory.ListUserParameters{
 | |
| 		PaginationArgs: &inventory.PaginationArgs{
 | |
| 			Page:     service.Page - 1,
 | |
| 			PageSize: service.PageSize,
 | |
| 			OrderBy:  service.OrderBy,
 | |
| 			Order:    inventory.OrderDirection(service.OrderDirection),
 | |
| 		},
 | |
| 		Status:  user.Status(service.Conditions[userStatusCondition]),
 | |
| 		GroupID: groupID,
 | |
| 		Nick:    service.Conditions[userNickCondition],
 | |
| 		Email:   service.Conditions[userEmailCondition],
 | |
| 	})
 | |
| 
 | |
| 	if err != nil {
 | |
| 		return nil, serializer.NewError(serializer.CodeDBError, "Failed to list users", err)
 | |
| 	}
 | |
| 
 | |
| 	return &ListUserResponse{
 | |
| 		Pagination: res.PaginationResults,
 | |
| 		Users: lo.Map(res.Users, func(user *ent.User, _ int) GetUserResponse {
 | |
| 			return GetUserResponse{
 | |
| 				User:         user,
 | |
| 				HashID:       hashid.EncodeUserID(hasher, user.ID),
 | |
| 				TwoFAEnabled: user.TwoFactorSecret != "",
 | |
| 			}
 | |
| 		}),
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| type (
 | |
| 	SingleUserService struct {
 | |
| 		ID int `uri:"id" json:"id" binding:"required"`
 | |
| 	}
 | |
| 	SingleUserParamCtx struct{}
 | |
| )
 | |
| 
 | |
| func (service *SingleUserService) Get(c *gin.Context) (*GetUserResponse, error) {
 | |
| 	dep := dependency.FromContext(c)
 | |
| 	hasher := dep.HashIDEncoder()
 | |
| 	userClient := dep.UserClient()
 | |
| 
 | |
| 	ctx := context.WithValue(c, inventory.LoadUserGroup{}, true)
 | |
| 	ctx = context.WithValue(ctx, inventory.LoadUserPasskey{}, true)
 | |
| 
 | |
| 	user, err := userClient.GetByID(ctx, service.ID)
 | |
| 	if err != nil {
 | |
| 		return nil, serializer.NewError(serializer.CodeDBError, "Failed to get user", err)
 | |
| 	}
 | |
| 
 | |
| 	m := manager.NewFileManager(dep, user)
 | |
| 	capacity, err := m.Capacity(ctx)
 | |
| 	if err != nil {
 | |
| 		return nil, serializer.NewError(serializer.CodeInternalSetting, "Failed to get user capacity", err)
 | |
| 	}
 | |
| 
 | |
| 	return &GetUserResponse{
 | |
| 		User:         user,
 | |
| 		HashID:       hashid.EncodeUserID(hasher, user.ID),
 | |
| 		TwoFAEnabled: user.TwoFactorSecret != "",
 | |
| 		Capacity:     capacity,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| func (service *SingleUserService) CalibrateStorage(c *gin.Context) (*GetUserResponse, error) {
 | |
| 	dep := dependency.FromContext(c)
 | |
| 	userClient := dep.UserClient()
 | |
| 
 | |
| 	ctx := context.WithValue(c, inventory.LoadUserGroup{}, true)
 | |
| 	_, err := userClient.CalculateStorage(ctx, service.ID)
 | |
| 	if err != nil {
 | |
| 		return nil, serializer.NewError(serializer.CodeDBError, "Failed to calculate storage", err)
 | |
| 	}
 | |
| 
 | |
| 	subService := &SingleUserService{ID: service.ID}
 | |
| 	return subService.Get(c)
 | |
| }
 | |
| 
 | |
| type (
 | |
| 	UpsertUserService struct {
 | |
| 		User     *ent.User `json:"user" binding:"required"`
 | |
| 		Password string    `json:"password"`
 | |
| 		TwoFA    string    `json:"two_fa"`
 | |
| 	}
 | |
| 	UpsertUserParamCtx struct{}
 | |
| )
 | |
| 
 | |
| func (s *UpsertUserService) Update(c *gin.Context) (*GetUserResponse, error) {
 | |
| 	dep := dependency.FromContext(c)
 | |
| 	userClient := dep.UserClient()
 | |
| 
 | |
| 	ctx := context.WithValue(c, inventory.LoadUserGroup{}, true)
 | |
| 	existing, err := userClient.GetByID(ctx, s.User.ID)
 | |
| 	if err != nil {
 | |
| 		return nil, serializer.NewError(serializer.CodeDBError, "Failed to get user", err)
 | |
| 	}
 | |
| 
 | |
| 	if s.User.ID == 1 && existing.Edges.Group.Permissions.Enabled(int(types.GroupPermissionIsAdmin)) {
 | |
| 		if s.User.GroupUsers != existing.GroupUsers {
 | |
| 			return nil, serializer.NewError(serializer.CodeInvalidActionOnDefaultUser, "Cannot change default user's group", nil)
 | |
| 		}
 | |
| 
 | |
| 		if s.User.Status != user.StatusActive {
 | |
| 			return nil, serializer.NewError(serializer.CodeInvalidActionOnDefaultUser, "Cannot change default user's status", nil)
 | |
| 		}
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	if s.Password != "" && len(s.Password) > 128 {
 | |
| 		return nil, serializer.NewError(serializer.CodeParamErr, "Password too long", nil)
 | |
| 	}
 | |
| 
 | |
| 	newUser, err := userClient.Upsert(ctx, s.User, s.Password, s.TwoFA)
 | |
| 	if err != nil {
 | |
| 		return nil, serializer.NewError(serializer.CodeDBError, "Failed to update user", err)
 | |
| 	}
 | |
| 
 | |
| 	service := &SingleUserService{ID: newUser.ID}
 | |
| 	return service.Get(c)
 | |
| }
 | |
| 
 | |
| func (s *UpsertUserService) Create(c *gin.Context) (*GetUserResponse, error) {
 | |
| 	dep := dependency.FromContext(c)
 | |
| 	userClient := dep.UserClient()
 | |
| 
 | |
| 	if s.Password == "" {
 | |
| 		return nil, serializer.NewError(serializer.CodeParamErr, "Password is required", nil)
 | |
| 	}
 | |
| 
 | |
| 	if s.User.ID != 0 {
 | |
| 		return nil, serializer.NewError(serializer.CodeParamErr, "ID must be 0", nil)
 | |
| 	}
 | |
| 
 | |
| 	user, err := userClient.Upsert(c, s.User, s.Password, s.TwoFA)
 | |
| 	if err != nil {
 | |
| 		return nil, serializer.NewError(serializer.CodeDBError, "Failed to create user", err)
 | |
| 	}
 | |
| 
 | |
| 	service := &SingleUserService{ID: user.ID}
 | |
| 	return service.Get(c)
 | |
| 
 | |
| }
 | |
| 
 | |
| type (
 | |
| 	BatchUserService struct {
 | |
| 		IDs []int `json:"ids" binding:"min=1"`
 | |
| 	}
 | |
| 	BatchUserParamCtx struct{}
 | |
| )
 | |
| 
 | |
| func (s *BatchUserService) Delete(c *gin.Context) error {
 | |
| 	dep := dependency.FromContext(c)
 | |
| 	userClient := dep.UserClient()
 | |
| 	fileClient := dep.FileClient()
 | |
| 
 | |
| 	current := inventory.UserFromContext(c)
 | |
| 	ae := serializer.NewAggregateError()
 | |
| 	for _, id := range s.IDs {
 | |
| 		if current.ID == id || id == 1 {
 | |
| 			ae.Add(strconv.Itoa(id), serializer.NewError(serializer.CodeInvalidActionOnDefaultUser, "Cannot delete current user", nil))
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		fc, tx, ctx, err := inventory.WithTx(c, fileClient)
 | |
| 		if err != nil {
 | |
| 			ae.Add(strconv.Itoa(id), serializer.NewError(serializer.CodeDBError, "Failed to start transaction", err))
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		uc, _, ctx, err := inventory.WithTx(ctx, userClient)
 | |
| 		if err != nil {
 | |
| 			ae.Add(strconv.Itoa(id), serializer.NewError(serializer.CodeDBError, "Failed to start transaction", err))
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if err := fc.DeleteByUser(ctx, id); err != nil {
 | |
| 			_ = inventory.Rollback(tx)
 | |
| 			ae.Add(strconv.Itoa(id), serializer.NewError(serializer.CodeDBError, "Failed to delete user files", err))
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if err := uc.Delete(ctx, id); err != nil {
 | |
| 			_ = inventory.Rollback(tx)
 | |
| 			ae.Add(strconv.Itoa(id), serializer.NewError(serializer.CodeDBError, "Failed to delete user", err))
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if err := inventory.Commit(tx); err != nil {
 | |
| 			ae.Add(strconv.Itoa(id), serializer.NewError(serializer.CodeDBError, "Failed to commit transaction", err))
 | |
| 			continue
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return ae.Aggregate()
 | |
| }
 | 
