mirror of https://github.com/cloudreve/Cloudreve
538 lines
16 KiB
Go
538 lines
16 KiB
Go
package admin
|
|
|
|
import (
|
|
"context"
|
|
"path"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/cloudreve/Cloudreve/v4/application/dependency"
|
|
"github.com/cloudreve/Cloudreve/v4/ent"
|
|
"github.com/cloudreve/Cloudreve/v4/inventory"
|
|
"github.com/cloudreve/Cloudreve/v4/inventory/types"
|
|
"github.com/cloudreve/Cloudreve/v4/pkg/cluster/routes"
|
|
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs"
|
|
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/manager"
|
|
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/manager/entitysource"
|
|
"github.com/cloudreve/Cloudreve/v4/pkg/hashid"
|
|
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/samber/lo"
|
|
)
|
|
|
|
// FileService 文件ID服务
|
|
type FileService struct {
|
|
ID uint `uri:"id" json:"id" binding:"required"`
|
|
}
|
|
|
|
// FileBatchService 文件批量操作服务
|
|
type FileBatchService struct {
|
|
ID []uint `json:"id" binding:"min=1"`
|
|
Force bool `json:"force"`
|
|
UnlinkOnly bool `json:"unlink"`
|
|
}
|
|
|
|
// ListFolderService 列目录结构
|
|
type ListFolderService struct {
|
|
Path string `uri:"path" binding:"required,max=65535"`
|
|
ID uint `uri:"id" binding:"required"`
|
|
Type string `uri:"type" binding:"eq=policy|eq=user"`
|
|
}
|
|
|
|
// List 列出指定路径下的目录
|
|
func (service *ListFolderService) List(c *gin.Context) serializer.Response {
|
|
//if service.Type == "policy" {
|
|
// // 列取存储策略中的目录
|
|
// policy, err := model.GetPolicyByID(service.ID)
|
|
// if err != nil {
|
|
// return serializer.ErrDeprecated(serializer.CodePolicyNotExist, "", err)
|
|
// }
|
|
//
|
|
// // 创建文件系统
|
|
// fs, err := filesystem.NewAnonymousFileSystem()
|
|
// if err != nil {
|
|
// return serializer.ErrDeprecated(serializer.CodeCreateFSError, "", err)
|
|
// }
|
|
// defer fs.Recycle()
|
|
//
|
|
// // 列取存储策略中的文件
|
|
// fs.Policy = &policy
|
|
// res, err := fs.ListPhysical(c.Request.Context(), service.Path)
|
|
// if err != nil {
|
|
// return serializer.ErrDeprecated(serializer.CodeListFilesError, "", err)
|
|
// }
|
|
//
|
|
// return serializer.Response{
|
|
// Data: serializer.BuildObjectList(0, res, nil),
|
|
// }
|
|
//
|
|
//}
|
|
//
|
|
//// 列取用户空间目录
|
|
//// 查找用户
|
|
//user, err := model.GetUserByID(service.ID)
|
|
//if err != nil {
|
|
// return serializer.ErrDeprecated(serializer.CodeUserNotFound, "", err)
|
|
//}
|
|
//
|
|
//// 创建文件系统
|
|
//fs, err := filesystem.NewFileSystem(&user)
|
|
//if err != nil {
|
|
// return serializer.ErrDeprecated(serializer.CodeCreateFSError, "", err)
|
|
//}
|
|
//defer fs.Recycle()
|
|
//
|
|
//// 列取目录
|
|
//res, err := fs.List(c.Request.Context(), service.Path, nil)
|
|
//if err != nil {
|
|
// return serializer.ErrDeprecated(serializer.CodeListFilesError, "", err)
|
|
//}
|
|
|
|
//return serializer.Response{
|
|
// Data: serializer.BuildObjectList(0, res, nil),
|
|
//}
|
|
|
|
return serializer.Response{}
|
|
}
|
|
|
|
// Delete 删除文件
|
|
func (service *FileBatchService) Delete(c *gin.Context) serializer.Response {
|
|
//files, err := model.GetFilesByIDs(service.ID, 0)
|
|
//if err != nil {
|
|
// return serializer.DBErrDeprecated("Failed to list files for deleting", err)
|
|
//}
|
|
//
|
|
//// 根据用户分组
|
|
//userFile := make(map[uint][]model.File)
|
|
//for i := 0; i < len(files); i++ {
|
|
// if _, ok := userFile[files[i].UserID]; !ok {
|
|
// userFile[files[i].UserID] = []model.File{}
|
|
// }
|
|
// userFile[files[i].UserID] = append(userFile[files[i].UserID], files[i])
|
|
//}
|
|
//
|
|
//// 异步执行删除
|
|
//go func(files map[uint][]model.File) {
|
|
// for uid, file := range files {
|
|
// var (
|
|
// fs *filesystem.FileSystem
|
|
// err error
|
|
// )
|
|
// user, err := model.GetUserByID(uid)
|
|
// if err != nil {
|
|
// fs, err = filesystem.NewAnonymousFileSystem()
|
|
// if err != nil {
|
|
// continue
|
|
// }
|
|
// } else {
|
|
// fs, err = filesystem.NewFileSystem(&user)
|
|
// if err != nil {
|
|
// fs.Recycle()
|
|
// continue
|
|
// }
|
|
// }
|
|
//
|
|
// // 汇总文件ID
|
|
// ids := make([]uint, 0, len(file))
|
|
// for i := 0; i < len(file); i++ {
|
|
// ids = append(ids, file[i].ID)
|
|
// }
|
|
//
|
|
// // 执行删除
|
|
// fs.Delete(context.Background(), []uint{}, ids, service.Force, service.UnlinkOnly)
|
|
// fs.Recycle()
|
|
// }
|
|
//}(userFile)
|
|
|
|
// 分组执行删除
|
|
return serializer.Response{}
|
|
|
|
}
|
|
|
|
const (
|
|
fileNameCondition = "file_name"
|
|
fileUserCondition = "file_user"
|
|
filePolicyCondition = "file_policy"
|
|
)
|
|
|
|
func (service *AdminListService) Files(c *gin.Context) (*ListFileResponse, error) {
|
|
dep := dependency.FromContext(c)
|
|
hasher := dep.HashIDEncoder()
|
|
fileClient := dep.FileClient()
|
|
|
|
ctx := context.WithValue(c, inventory.LoadFileEntity{}, true)
|
|
ctx = context.WithValue(ctx, inventory.LoadFileMetadata{}, true)
|
|
ctx = context.WithValue(ctx, inventory.LoadFileShare{}, true)
|
|
ctx = context.WithValue(ctx, inventory.LoadFileUser{}, true)
|
|
ctx = context.WithValue(ctx, inventory.LoadFileDirectLink{}, true)
|
|
|
|
var (
|
|
err error
|
|
userID int
|
|
policyID int
|
|
)
|
|
|
|
if service.Conditions[fileUserCondition] != "" {
|
|
userID, err = strconv.Atoi(service.Conditions[fileUserCondition])
|
|
if err != nil {
|
|
return nil, serializer.NewError(serializer.CodeParamErr, "Invalid user ID", err)
|
|
}
|
|
}
|
|
|
|
if service.Conditions[filePolicyCondition] != "" {
|
|
policyID, err = strconv.Atoi(service.Conditions[filePolicyCondition])
|
|
if err != nil {
|
|
return nil, serializer.NewError(serializer.CodeParamErr, "Invalid policy ID", err)
|
|
}
|
|
}
|
|
|
|
res, err := fileClient.FlattenListFiles(ctx, &inventory.FlattenListFileParameters{
|
|
PaginationArgs: &inventory.PaginationArgs{
|
|
Page: service.Page - 1,
|
|
PageSize: service.PageSize,
|
|
OrderBy: service.OrderBy,
|
|
Order: inventory.OrderDirection(service.OrderDirection),
|
|
},
|
|
UserID: userID,
|
|
StoragePolicyID: policyID,
|
|
Name: service.Conditions[fileNameCondition],
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, serializer.NewError(serializer.CodeDBError, "Failed to list files", err)
|
|
}
|
|
|
|
return &ListFileResponse{
|
|
Pagination: res.PaginationResults,
|
|
Files: lo.Map(res.Files, func(file *ent.File, _ int) GetFileResponse {
|
|
return GetFileResponse{
|
|
File: file,
|
|
UserHashID: hashid.EncodeUserID(hasher, file.OwnerID),
|
|
FileHashID: hashid.EncodeFileID(hasher, file.ID),
|
|
}
|
|
}),
|
|
}, nil
|
|
}
|
|
|
|
type (
|
|
SingleFileService struct {
|
|
ID int `uri:"id" json:"id" binding:"required"`
|
|
}
|
|
SingleFileParamCtx struct{}
|
|
)
|
|
|
|
func (service *SingleFileService) Get(c *gin.Context) (*GetFileResponse, error) {
|
|
dep := dependency.FromContext(c)
|
|
hasher := dep.HashIDEncoder()
|
|
fileClient := dep.FileClient()
|
|
|
|
ctx := context.WithValue(c, inventory.LoadFileEntity{}, true)
|
|
ctx = context.WithValue(ctx, inventory.LoadFileMetadata{}, true)
|
|
ctx = context.WithValue(ctx, inventory.LoadFileShare{}, true)
|
|
ctx = context.WithValue(ctx, inventory.LoadFileUser{}, true)
|
|
ctx = context.WithValue(ctx, inventory.LoadEntityUser{}, true)
|
|
ctx = context.WithValue(ctx, inventory.LoadEntityStoragePolicy{}, true)
|
|
ctx = context.WithValue(ctx, inventory.LoadFileDirectLink{}, true)
|
|
|
|
file, err := fileClient.GetByID(ctx, service.ID)
|
|
if err != nil {
|
|
if ent.IsNotFound(err) {
|
|
return nil, serializer.NewError(serializer.CodeNotFound, "File not found", nil)
|
|
}
|
|
|
|
return nil, serializer.NewError(serializer.CodeDBError, "Failed to get file", err)
|
|
}
|
|
|
|
directLinkMap := make(map[int]string)
|
|
siteURL := dep.SettingProvider().SiteURL(c)
|
|
for _, directLink := range file.Edges.DirectLinks {
|
|
directLinkMap[directLink.ID] = routes.MasterDirectLink(siteURL, hashid.EncodeSourceLinkID(hasher, directLink.ID), directLink.Name).String()
|
|
}
|
|
|
|
return &GetFileResponse{
|
|
File: file,
|
|
UserHashID: hashid.EncodeUserID(hasher, file.OwnerID),
|
|
FileHashID: hashid.EncodeFileID(hasher, file.ID),
|
|
DirectLinkMap: directLinkMap,
|
|
}, nil
|
|
}
|
|
|
|
type (
|
|
UpsertFileService struct {
|
|
File *ent.File `json:"file" binding:"required"`
|
|
}
|
|
UpsertFileParamCtx struct{}
|
|
)
|
|
|
|
func (s *UpsertFileService) Update(c *gin.Context) (*GetFileResponse, error) {
|
|
dep := dependency.FromContext(c)
|
|
fileClient := dep.FileClient()
|
|
|
|
fc, tx, ctx, err := inventory.WithTx(c, fileClient)
|
|
if err != nil {
|
|
return nil, serializer.NewError(serializer.CodeDBError, "Failed to start transaction", err)
|
|
}
|
|
|
|
newFile, err := fc.Update(ctx, s.File)
|
|
if err != nil {
|
|
_ = inventory.Rollback(tx)
|
|
return nil, serializer.NewError(serializer.CodeDBError, "Failed to update file", err)
|
|
}
|
|
|
|
if err := inventory.Commit(tx); err != nil {
|
|
return nil, serializer.NewError(serializer.CodeDBError, "Failed to commit transaction", err)
|
|
}
|
|
|
|
service := &SingleFileService{ID: newFile.ID}
|
|
return service.Get(c)
|
|
}
|
|
|
|
func (s *SingleFileService) Url(c *gin.Context) (string, error) {
|
|
dep := dependency.FromContext(c)
|
|
fileClient := dep.FileClient()
|
|
|
|
ctx := context.WithValue(c, inventory.LoadFileEntity{}, true)
|
|
file, err := fileClient.GetByID(ctx, s.ID)
|
|
if err != nil {
|
|
return "", serializer.NewError(serializer.CodeDBError, "Failed to get file", err)
|
|
}
|
|
|
|
// find primary entity
|
|
var primaryEntity *ent.Entity
|
|
for _, entity := range file.Edges.Entities {
|
|
if entity.Type == int(types.EntityTypeVersion) && entity.ID == file.PrimaryEntity {
|
|
primaryEntity = entity
|
|
break
|
|
}
|
|
}
|
|
|
|
if primaryEntity == nil {
|
|
return "", serializer.NewError(serializer.CodeNotFound, "Primary entity not exist", nil)
|
|
}
|
|
|
|
// find policy
|
|
policy, err := dep.StoragePolicyClient().GetPolicyByID(ctx, primaryEntity.StoragePolicyEntities)
|
|
if err != nil {
|
|
return "", serializer.NewError(serializer.CodeDBError, "Failed to get policy", err)
|
|
}
|
|
|
|
m := manager.NewFileManager(dep, inventory.UserFromContext(c))
|
|
defer m.Recycle()
|
|
|
|
driver, err := m.GetStorageDriver(ctx, policy)
|
|
if err != nil {
|
|
return "", serializer.NewError(serializer.CodeInternalSetting, "Failed to get storage driver", err)
|
|
}
|
|
|
|
es := entitysource.NewEntitySource(fs.NewEntity(primaryEntity), driver, policy, dep.GeneralAuth(),
|
|
dep.SettingProvider(), dep.HashIDEncoder(), dep.RequestClient(), dep.Logger(), dep.ConfigProvider(), dep.MimeDetector(ctx))
|
|
|
|
expire := time.Now().Add(time.Hour * 1)
|
|
url, err := es.Url(ctx, entitysource.WithExpire(&expire), entitysource.WithDisplayName(file.Name))
|
|
if err != nil {
|
|
return "", serializer.NewError(serializer.CodeInternalSetting, "Failed to get url", err)
|
|
}
|
|
|
|
return url.Url, nil
|
|
}
|
|
|
|
type (
|
|
BatchFileService struct {
|
|
IDs []int `json:"ids" binding:"min=1"`
|
|
}
|
|
BatchFileParamCtx struct{}
|
|
)
|
|
|
|
func (s *BatchFileService) Delete(c *gin.Context) error {
|
|
dep := dependency.FromContext(c)
|
|
fileClient := dep.FileClient()
|
|
|
|
ctx := context.WithValue(c, inventory.LoadFileEntity{}, true)
|
|
files, _, err := fileClient.GetByIDs(ctx, s.IDs, 0)
|
|
if err != nil {
|
|
return serializer.NewError(serializer.CodeDBError, "Failed to get files", err)
|
|
}
|
|
|
|
fc, tx, ctx, err := inventory.WithTx(c, fileClient)
|
|
if err != nil {
|
|
return serializer.NewError(serializer.CodeDBError, "Failed to start transaction", err)
|
|
}
|
|
|
|
_, diff, err := fc.Delete(ctx, files, nil)
|
|
if err != nil {
|
|
_ = inventory.Rollback(tx)
|
|
return serializer.NewError(serializer.CodeDBError, "Failed to delete files", err)
|
|
}
|
|
|
|
tx.AppendStorageDiff(diff)
|
|
if err := inventory.CommitWithStorageDiff(ctx, tx, dep.Logger(), dep.UserClient()); err != nil {
|
|
return serializer.NewError(serializer.CodeDBError, "Failed to commit transaction", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
const (
|
|
entityUserCondition = "entity_user"
|
|
entityPolicyCondition = "entity_policy"
|
|
entityTypeCondition = "entity_type"
|
|
)
|
|
|
|
func (s *AdminListService) Entities(c *gin.Context) (*ListEntityResponse, error) {
|
|
dep := dependency.FromContext(c)
|
|
fileClient := dep.FileClient()
|
|
hasher := dep.HashIDEncoder()
|
|
ctx := context.WithValue(c, inventory.LoadEntityUser{}, true)
|
|
ctx = context.WithValue(ctx, inventory.LoadEntityStoragePolicy{}, true)
|
|
|
|
var (
|
|
userID int
|
|
policyID int
|
|
err error
|
|
entityType *types.EntityType
|
|
)
|
|
|
|
if s.Conditions[entityUserCondition] != "" {
|
|
userID, err = strconv.Atoi(s.Conditions[entityUserCondition])
|
|
if err != nil {
|
|
return nil, serializer.NewError(serializer.CodeParamErr, "Invalid user ID", err)
|
|
}
|
|
}
|
|
|
|
if s.Conditions[entityPolicyCondition] != "" {
|
|
policyID, err = strconv.Atoi(s.Conditions[entityPolicyCondition])
|
|
if err != nil {
|
|
return nil, serializer.NewError(serializer.CodeParamErr, "Invalid policy ID", err)
|
|
}
|
|
}
|
|
|
|
if s.Conditions[entityTypeCondition] != "" {
|
|
typeId, err := strconv.Atoi(s.Conditions[entityTypeCondition])
|
|
if err != nil {
|
|
return nil, serializer.NewError(serializer.CodeParamErr, "Invalid entity type", err)
|
|
}
|
|
|
|
t := types.EntityType(typeId)
|
|
entityType = &t
|
|
}
|
|
|
|
res, err := fileClient.ListEntities(ctx, &inventory.ListEntityParameters{
|
|
PaginationArgs: &inventory.PaginationArgs{
|
|
Page: s.Page - 1,
|
|
PageSize: s.PageSize,
|
|
OrderBy: s.OrderBy,
|
|
Order: inventory.OrderDirection(s.OrderDirection),
|
|
},
|
|
UserID: userID,
|
|
StoragePolicyID: policyID,
|
|
EntityType: entityType,
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, serializer.NewError(serializer.CodeDBError, "Failed to list entities", err)
|
|
}
|
|
|
|
return &ListEntityResponse{
|
|
Pagination: res.PaginationResults,
|
|
Entities: lo.Map(res.Entities, func(entity *ent.Entity, _ int) GetEntityResponse {
|
|
return GetEntityResponse{
|
|
Entity: entity,
|
|
UserHashID: hashid.EncodeUserID(hasher, entity.CreatedBy),
|
|
}
|
|
}),
|
|
}, nil
|
|
}
|
|
|
|
type (
|
|
SingleEntityService struct {
|
|
ID int `uri:"id" json:"id" binding:"required"`
|
|
}
|
|
SingleEntityParamCtx struct{}
|
|
)
|
|
|
|
func (s *SingleEntityService) Get(c *gin.Context) (*GetEntityResponse, error) {
|
|
dep := dependency.FromContext(c)
|
|
fileClient := dep.FileClient()
|
|
hasher := dep.HashIDEncoder()
|
|
|
|
ctx := context.WithValue(c, inventory.LoadEntityUser{}, true)
|
|
ctx = context.WithValue(ctx, inventory.LoadEntityStoragePolicy{}, true)
|
|
ctx = context.WithValue(ctx, inventory.LoadEntityFile{}, true)
|
|
ctx = context.WithValue(ctx, inventory.LoadFileUser{}, true)
|
|
|
|
userHashIDMap := make(map[int]string)
|
|
entity, err := fileClient.GetEntityByID(ctx, s.ID)
|
|
if err != nil {
|
|
if ent.IsNotFound(err) {
|
|
return nil, serializer.NewError(serializer.CodeNotFound, "Entity not found", nil)
|
|
}
|
|
return nil, serializer.NewError(serializer.CodeDBError, "Failed to get entity", err)
|
|
}
|
|
|
|
for _, file := range entity.Edges.File {
|
|
userHashIDMap[file.OwnerID] = hashid.EncodeUserID(hasher, file.OwnerID)
|
|
}
|
|
|
|
return &GetEntityResponse{
|
|
Entity: entity,
|
|
UserHashID: hashid.EncodeUserID(hasher, entity.CreatedBy),
|
|
UserHashIDMap: userHashIDMap,
|
|
}, nil
|
|
}
|
|
|
|
type (
|
|
BatchEntityService struct {
|
|
IDs []int `json:"ids" binding:"min=1"`
|
|
Force bool `json:"force"`
|
|
}
|
|
BatchEntityParamCtx struct{}
|
|
)
|
|
|
|
func (s *BatchEntityService) Delete(c *gin.Context) error {
|
|
dep := dependency.FromContext(c)
|
|
m := manager.NewFileManager(dep, inventory.UserFromContext(c))
|
|
defer m.Recycle()
|
|
|
|
err := m.RecycleEntities(c.Request.Context(), s.Force, s.IDs...)
|
|
if err != nil {
|
|
return serializer.NewError(serializer.CodeDBError, "Failed to recycle entities", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *SingleEntityService) Url(c *gin.Context) (string, error) {
|
|
dep := dependency.FromContext(c)
|
|
fileClient := dep.FileClient()
|
|
|
|
entity, err := fileClient.GetEntityByID(c, s.ID)
|
|
if err != nil {
|
|
return "", serializer.NewError(serializer.CodeDBError, "Failed to get file", err)
|
|
}
|
|
|
|
// find policy
|
|
policy, err := dep.StoragePolicyClient().GetPolicyByID(c, entity.StoragePolicyEntities)
|
|
if err != nil {
|
|
return "", serializer.NewError(serializer.CodeDBError, "Failed to get policy", err)
|
|
}
|
|
|
|
m := manager.NewFileManager(dep, inventory.UserFromContext(c))
|
|
defer m.Recycle()
|
|
|
|
driver, err := m.GetStorageDriver(c, policy)
|
|
if err != nil {
|
|
return "", serializer.NewError(serializer.CodeInternalSetting, "Failed to get storage driver", err)
|
|
}
|
|
|
|
es := entitysource.NewEntitySource(fs.NewEntity(entity), driver, policy, dep.GeneralAuth(),
|
|
dep.SettingProvider(), dep.HashIDEncoder(), dep.RequestClient(), dep.Logger(), dep.ConfigProvider(), dep.MimeDetector(c))
|
|
|
|
expire := time.Now().Add(time.Hour * 1)
|
|
url, err := es.Url(c, entitysource.WithDownload(true), entitysource.WithExpire(&expire), entitysource.WithDisplayName(path.Base(entity.Source)))
|
|
if err != nil {
|
|
return "", serializer.NewError(serializer.CodeInternalSetting, "Failed to get url", err)
|
|
}
|
|
|
|
return url.Url, nil
|
|
}
|