feat(backend): support group folder

***Important: model field modified***
1. `model.File.UserID`: `uint` => `int`
2. `model.Folder.OwnerID`: `uint` => `int`
> Why? negative id means its a `group id`.
> Positive means it belongs to a user.
pull/1882/head
WintBit 2 years ago
parent 88409cc1f0
commit ee8f90df1c

@ -20,7 +20,7 @@ type File struct {
gorm.Model
Name string `gorm:"unique_index:idx_only_one"`
SourceName string `gorm:"type:text"`
UserID uint `gorm:"index:user_id;unique_index:idx_only_one"`
UserID int `gorm:"index:user_id;unique_index:idx_only_one"`
Size uint64
PicInfo string
FolderID uint `gorm:"index:folder_id;unique_index:idx_only_one"`
@ -63,8 +63,12 @@ func (file *File) Create() error {
return err
}
if file.UserID < 0 {
return tx.Commit().Error
}
user := &User{}
user.ID = file.UserID
user.ID = uint(file.UserID)
if err := user.ChangeStorage(tx, "+", file.Size); err != nil {
tx.Rollback()
return err
@ -254,7 +258,7 @@ func DeleteFiles(files []*File, uid uint) error {
user.ID = uid
var size uint64
for _, file := range files {
if uid > 0 && file.UserID != uid {
if uid > 0 && file.UserID > 0 && uint(file.UserID) != uid {
tx.Rollback()
return errors.New("user id not consistent")
}
@ -342,8 +346,6 @@ func (file *File) UpdateSize(value uint64) error {
tx := DB.Begin()
var sizeDelta uint64
operator := "+"
user := User{}
user.ID = file.UserID
if value > file.Size {
sizeDelta = value - file.Size
} else {
@ -367,12 +369,17 @@ func (file *File) UpdateSize(value uint64) error {
return res.Error
}
file.Size = value
if file.UserID < 0 {
return tx.Commit().Error
}
user := User{}
user.ID = uint(file.UserID)
if err := user.ChangeStorage(tx, operator, sizeDelta); err != nil {
tx.Rollback()
return err
}
file.Size = value
return tx.Commit().Error
}

@ -15,7 +15,7 @@ type Folder struct {
gorm.Model
Name string `gorm:"unique_index:idx_only_one_name"`
ParentID *uint `gorm:"index:parent_id;unique_index:idx_only_one_name"`
OwnerID uint `gorm:"index:owner_id"`
OwnerID int `gorm:"index:owner_id"`
// 数据库忽略字段
Position string `gorm:"-"`
@ -213,8 +213,14 @@ func (folder *Folder) MoveOrCopyFileTo(files []uint, dstFolder *Folder, isCopy b
// CopyFolderTo 将此目录及其子目录及文件递归复制至dstFolder
// 返回此操作新增的容量
func (folder *Folder) CopyFolderTo(folderID uint, dstFolder *Folder) (size uint64, err error) {
if folder.OwnerID < 0 {
return 0, errors.New("cannot copy group folder")
}
ownerId := uint(folder.OwnerID)
// 列出所有子目录
subFolders, err := GetRecursiveChildFolder([]uint{folderID}, folder.OwnerID, true)
subFolders, err := GetRecursiveChildFolder([]uint{folderID}, ownerId, true)
if err != nil {
return 0, err
}

@ -19,6 +19,7 @@ type Group struct {
// 数据库忽略字段
PolicyList []uint `gorm:"-"`
OptionsSerialized GroupOption `gorm:"-"`
GroupFolder *Folder `gorm:"-"`
}
// GroupOption 用户组其他配置
@ -82,3 +83,9 @@ func (group *Group) SerializePolicyList() (err error) {
group.Options = string(optionsValue)
return err
}
func (group *Group) getGroupFolder() (*Folder, bool) {
var folder Folder
result := DB.Where("owner_id = ?", -int(group.ID)).First(&folder)
return &folder, !result.RecordNotFound()
}

@ -137,6 +137,13 @@ func GetUserByID(ID interface{}) (User, error) {
func GetActiveUserByID(ID interface{}) (User, error) {
var user User
result := DB.Set("gorm:auto_preload", true).Where("status = ?", Active).First(&user, ID)
if user.Group.GroupFolder == nil {
folder, ok := user.Group.getGroupFolder()
if ok {
user.Group.GroupFolder = folder
}
}
return user, result.Error
}
@ -180,7 +187,7 @@ func (user *User) AfterCreate(tx *gorm.DB) (err error) {
// 创建用户的默认根目录
defaultFolder := &Folder{
Name: "/",
OwnerID: user.ID,
OwnerID: int(user.ID),
}
tx.Create(defaultFolder)
return err
@ -198,7 +205,7 @@ func (user *User) AfterFind() (err error) {
return err
}
//SerializeOptions 将序列后的Option写入到数据库字段
// SerializeOptions 将序列后的Option写入到数据库字段
func (user *User) SerializeOptions() (err error) {
optionsValue, err := json.Marshal(&user.OptionsSerialized)
user.Options = string(optionsValue)

@ -60,7 +60,7 @@ func uploadSessionCollect() {
placeholders := model.GetUploadPlaceholderFiles(0)
// 将过期的上传会话按照用户分组
userToFiles := make(map[uint][]uint)
userToFiles := make(map[int][]uint)
for _, file := range placeholders {
_, sessionExist := cache.Get(filesystem.UploadSessionCachePrefix + *file.UploadSessionID)
if sessionExist {

@ -56,7 +56,7 @@ func (fs *FileSystem) AddFile(ctx context.Context, parent *model.Folder, file fs
newFile := model.File{
Name: uploadInfo.FileName,
SourceName: uploadInfo.SavePath,
UserID: fs.User.ID,
UserID: int(fs.User.ID),
Size: uploadInfo.Size,
FolderID: parent.ID,
PolicyID: fs.Policy.ID,

@ -3,14 +3,13 @@ package filesystem
import (
"context"
"fmt"
"path"
"strings"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/hashid"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"path"
"strings"
)
/* =================
@ -288,6 +287,12 @@ func (fs *FileSystem) List(ctx context.Context, dirPath string, pathProcessor fu
// 获取子文件
childFiles, _ = folder.GetChildFiles()
if dirPath == "/" {
if fs.User.Group.GroupFolder != nil {
childFolders = append(childFolders, *fs.User.Group.GroupFolder)
}
}
return fs.listObjects(ctx, parentPath, childFiles, childFolders, pathProcessor), nil
}
@ -336,15 +341,18 @@ func (fs *FileSystem) listObjects(ctx context.Context, parent string, files []mo
// 所有对象的父目录
var processedPath string
// 路径处理钩子,
// 所有对象父目录都是一样的,所以只处理一次
if pathProcessor != nil {
processedPath = pathProcessor(parent)
} else {
processedPath = parent
}
for _, subFolder := range folders {
// 路径处理钩子,
// 所有对象父目录都是一样的,所以只处理一次
if processedPath == "" {
if pathProcessor != nil {
processedPath = pathProcessor(parent)
} else {
processedPath = parent
}
var key string
if subFolder.OwnerID < 0 {
key = "group"
}
objects = append(objects, serializer.Object{
@ -353,20 +361,13 @@ func (fs *FileSystem) listObjects(ctx context.Context, parent string, files []mo
Path: processedPath,
Size: 0,
Type: "dir",
Key: key,
Date: subFolder.UpdatedAt,
CreateDate: subFolder.CreatedAt,
})
}
for _, file := range files {
if processedPath == "" {
if pathProcessor != nil {
processedPath = pathProcessor(parent)
} else {
processedPath = parent
}
}
if file.UploadSessionID == nil {
newFile := serializer.Object{
ID: hashid.HashID(file.ID, hashid.FileID),
@ -431,11 +432,17 @@ func (fs *FileSystem) CreateDirectory(ctx context.Context, fullPath string) (*mo
return nil, ErrFileExisted
}
if base == "/" &&
fs.User.Group.GroupFolder != nil &&
fs.User.Group.GroupFolder.Name == dir {
return fs.User.Group.GroupFolder, nil
}
// 创建目录
newFolder := model.Folder{
Name: dir,
ParentID: &parent.ID,
OwnerID: fs.User.ID,
OwnerID: parent.OwnerID,
}
_, err := newFolder.Create()

@ -29,11 +29,11 @@ func (fs *FileSystem) IsPathExist(path string) (bool, *model.Folder) {
currentFolder = fs.Root
}
for _, folderName := range pathList {
for i, folderName := range pathList {
var err error
// 根目录
if folderName == "/" {
if i == 0 && folderName == "/" {
if currentFolder != nil {
continue
}
@ -42,6 +42,13 @@ func (fs *FileSystem) IsPathExist(path string) (bool, *model.Folder) {
return false, nil
}
} else {
if i == 1 {
groupFolder := fs.User.Group.GroupFolder
if groupFolder != nil && folderName == groupFolder.Name {
currentFolder = groupFolder
continue
}
}
currentFolder, err = currentFolder.GetChild(folderName)
if err != nil {
return false, nil

@ -56,7 +56,11 @@ func HashDecode(raw string) ([]int, error) {
// HashID 计算数据库内主键对应的HashID
func HashID(id uint, t int) string {
v, _ := HashEncode([]int{int(id), t})
return HashIDInt(int(id), t)
}
func HashIDInt(id int, t int) string {
v, _ := HashEncode([]int{id, t})
return v
}

@ -10,7 +10,7 @@ type WopiClientMock struct {
mock.Mock
}
func (w *WopiClientMock) NewSession(user uint, file *model.File, action wopi.ActonType) (*wopi.Session, error) {
func (w *WopiClientMock) NewSession(user int, file *model.File, action wopi.ActonType) (*wopi.Session, error) {
args := w.Called(user, file, action)
return args.Get(0).(*wopi.Session), args.Error(1)
}

@ -59,7 +59,7 @@ type Session struct {
type SessionCache struct {
AccessToken string
FileID uint
UserID uint
UserID int
Action ActonType
}

@ -18,7 +18,7 @@ import (
type Client interface {
// NewSession creates a new document session with access token.
NewSession(uid uint, file *model.File, action ActonType) (*Session, error)
NewSession(uid int, file *model.File, action ActonType) (*Session, error)
// AvailableExts returns a list of file extensions that are supported by WOPI.
AvailableExts() []string
}
@ -116,7 +116,7 @@ func NewClient(endpoint string, cache cache.Driver, http request.Client) (Client
}, nil
}
func (c *client) NewSession(uid uint, file *model.File, action ActonType) (*Session, error) {
func (c *client) NewSession(uid int, file *model.File, action ActonType) (*Session, error) {
if err := c.checkDiscovery(); err != nil {
return nil, err
}

@ -93,7 +93,7 @@ func (service *FileBatchService) Delete(c *gin.Context) serializer.Response {
}
// 根据用户分组
userFile := make(map[uint][]model.File)
userFile := make(map[int][]model.File)
for i := 0; i < len(files); i++ {
if _, ok := userFile[files[i].UserID]; !ok {
userFile[files[i].UserID] = []model.File{}
@ -102,7 +102,7 @@ func (service *FileBatchService) Delete(c *gin.Context) serializer.Response {
}
// 异步执行删除
go func(files map[uint][]model.File) {
go func(files map[int][]model.File) {
for uid, file := range files {
var (
fs *filesystem.FileSystem
@ -183,12 +183,12 @@ func (service *AdminListService) Files() serializer.Response {
tx.Limit(service.PageSize).Offset((service.Page - 1) * service.PageSize).Find(&res)
// 查询对应用户
users := make(map[uint]model.User)
users := make(map[int]model.User)
for _, file := range res {
users[file.UserID] = model.User{}
}
userIDs := make([]uint, 0, len(users))
userIDs := make([]int, 0, len(users))
for k := range users {
userIDs = append(userIDs, k)
}
@ -197,7 +197,7 @@ func (service *AdminListService) Files() serializer.Response {
model.DB.Where("id in (?)", userIDs).Find(&userList)
for _, v := range userList {
users[v.ID] = v
users[int(v.ID)] = v
}
return serializer.Response{Data: map[string]interface{}{

@ -94,7 +94,7 @@ func (service *WopiService) FileInfo(c *gin.Context) (*serializer.WopiFileInfo,
ReadOnly: true,
ClosePostMessage: true,
Size: int64(fs.FileTarget[0].Size),
OwnerId: hashid.HashID(fs.FileTarget[0].UserID, hashid.UserID),
OwnerId: hashid.HashIDInt(fs.FileTarget[0].UserID, hashid.UserID),
}
if session.Action == wopi.ActionEdit {

Loading…
Cancel
Save