From 51fa9f66a5d4dc7c8da63c28a4390864ff3ef7ed Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Thu, 29 May 2025 09:44:11 +0800 Subject: [PATCH] feat(dashboard): traverse file URI from file ID (#2412) --- assets | 2 +- pkg/filemanager/fs/dbfs/dbfs.go | 2 +- pkg/filemanager/fs/dbfs/manage.go | 34 ++++++++++++++++++++++++++++ pkg/filemanager/fs/fs.go | 2 ++ pkg/filemanager/manager/manager.go | 2 ++ pkg/filemanager/manager/operation.go | 4 ++++ service/admin/file.go | 2 ++ service/admin/response.go | 1 + service/explorer/file.go | 21 ++++++++++++++++- 9 files changed, 67 insertions(+), 3 deletions(-) diff --git a/assets b/assets index 2440a06..d674a23 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 2440a06536dd2b8053359999bb61d495220cbf9c +Subproject commit d674a23b21bdeb0a415985d4d5dc2b2051bc80d1 diff --git a/pkg/filemanager/fs/dbfs/dbfs.go b/pkg/filemanager/fs/dbfs/dbfs.go index 96f0d62..eb9cdfb 100644 --- a/pkg/filemanager/fs/dbfs/dbfs.go +++ b/pkg/filemanager/fs/dbfs/dbfs.go @@ -450,7 +450,7 @@ func (f *DBFS) Get(ctx context.Context, path *fs.URI, opts ...fs.Option) (fs.Fil return nil, fmt.Errorf("failed to get target file: %w", err) } - if o.notRoot && target.IsRootFolder() { + if o.notRoot && (target == nil || target.IsRootFolder()) { return nil, fs.ErrNotSupportedAction.WithError(fmt.Errorf("cannot operate root file")) } diff --git a/pkg/filemanager/fs/dbfs/manage.go b/pkg/filemanager/fs/dbfs/manage.go index 3492115..00d5542 100644 --- a/pkg/filemanager/fs/dbfs/manage.go +++ b/pkg/filemanager/fs/dbfs/manage.go @@ -641,6 +641,40 @@ func (f *DBFS) GetFileFromDirectLink(ctx context.Context, dl *ent.DirectLink) (f return file, nil } +func (f *DBFS) TraverseFile(ctx context.Context, fileID int) (fs.File, error) { + fileModel, err := f.fileClient.GetByID(ctx, fileID) + if err != nil { + return nil, err + } + + if fileModel.OwnerID != f.user.ID && !f.user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionIsAdmin)) { + return nil, fs.ErrOwnerOnly.WithError(fmt.Errorf("only file owner can traverse file's uri")) + } + + file := newFile(nil, fileModel) + + // Traverse to the root file + baseNavigator := newBaseNavigator(f.fileClient, defaultFilter, f.user, f.hasher, f.settingClient.DBFS(ctx)) + root, err := baseNavigator.findRoot(ctx, file) + if err != nil { + return nil, fmt.Errorf("failed to find root file: %w", err) + } + + rootUri := newMyUri() + if fileModel.OwnerID != f.user.ID { + rootUri = newMyIDUri(hashid.EncodeUserID(f.hasher, fileModel.OwnerID)) + } + + if root.Name() != inventory.RootFolderName { + rootUri = newTrashUri(root.Name()) + } + + root.Path[pathIndexRoot] = rootUri + root.Path[pathIndexUser] = rootUri + + return file, nil +} + func (f *DBFS) deleteEntity(ctx context.Context, target *File, entityId int) (inventory.StorageDiff, error) { if target.PrimaryEntityID() == entityId { return nil, fs.ErrNotSupportedAction.WithError(fmt.Errorf("cannot delete current version")) diff --git a/pkg/filemanager/fs/fs.go b/pkg/filemanager/fs/fs.go index 25c9532..b6bd6c7 100644 --- a/pkg/filemanager/fs/fs.go +++ b/pkg/filemanager/fs/fs.go @@ -95,6 +95,8 @@ type ( VersionControl(ctx context.Context, path *URI, versionId int, delete bool) error // GetFileFromDirectLink gets a file from a direct link. GetFileFromDirectLink(ctx context.Context, dl *ent.DirectLink) (File, error) + // TraverseFile traverses a file to its root file, return the file with linked root. + TraverseFile(ctx context.Context, fileID int) (File, error) } UploadManager interface { diff --git a/pkg/filemanager/manager/manager.go b/pkg/filemanager/manager/manager.go index d8dc2fe..6735f4a 100644 --- a/pkg/filemanager/manager/manager.go +++ b/pkg/filemanager/manager/manager.go @@ -55,6 +55,8 @@ type ( PatchMedata(ctx context.Context, path []*fs.URI, data ...fs.MetadataPatch) error // CreateViewerSession creates a viewer session for given file CreateViewerSession(ctx context.Context, uri *fs.URI, version string, viewer *setting.Viewer) (*ViewerSession, error) + // TraverseFile traverses a file to its root file, return the file with linked root. + TraverseFile(ctx context.Context, fileID int) (fs.File, error) } FsManagement interface { diff --git a/pkg/filemanager/manager/operation.go b/pkg/filemanager/manager/operation.go index 0df8951..a45d849 100644 --- a/pkg/filemanager/manager/operation.go +++ b/pkg/filemanager/manager/operation.go @@ -277,6 +277,10 @@ func (l *manager) CreateOrUpdateShare(ctx context.Context, path *fs.URI, args *C return share, nil } +func (m *manager) TraverseFile(ctx context.Context, fileID int) (fs.File, error) { + return m.fs.TraverseFile(ctx, fileID) +} + func getEntityDisplayName(f fs.File, e fs.Entity) string { switch e.Type() { case types.EntityTypeThumbnail: diff --git a/service/admin/file.go b/service/admin/file.go index de08a7e..4e46db0 100644 --- a/service/admin/file.go +++ b/service/admin/file.go @@ -208,6 +208,7 @@ func (service *AdminListService) Files(c *gin.Context) (*ListFileResponse, error return GetFileResponse{ File: file, UserHashID: hashid.EncodeUserID(hasher, file.OwnerID), + FileHashID: hashid.EncodeFileID(hasher, file.ID), } }), }, nil @@ -251,6 +252,7 @@ func (service *SingleFileService) Get(c *gin.Context) (*GetFileResponse, error) return &GetFileResponse{ File: file, UserHashID: hashid.EncodeUserID(hasher, file.OwnerID), + FileHashID: hashid.EncodeFileID(hasher, file.ID), DirectLinkMap: directLinkMap, }, nil } diff --git a/service/admin/response.go b/service/admin/response.go index 25de571..49afbe0 100644 --- a/service/admin/response.go +++ b/service/admin/response.go @@ -54,6 +54,7 @@ type ListFileResponse struct { type GetFileResponse struct { *ent.File UserHashID string `json:"user_hash_id,omitempty"` + FileHashID string `json:"file_hash_id,omitempty"` DirectLinkMap map[int]string `json:"direct_link_map,omitempty"` } diff --git a/service/explorer/file.go b/service/explorer/file.go index 7fa77f3..615c927 100644 --- a/service/explorer/file.go +++ b/service/explorer/file.go @@ -584,7 +584,8 @@ func (s *UnlockFileService) Unlock(c *gin.Context) error { type ( GetFileInfoParameterCtx struct{} GetFileInfoService struct { - Uri string `form:"uri" binding:"required"` + Uri string `form:"uri"` + ID string `form:"id"` ExtendedInfo bool `form:"extended"` FolderSummary bool `form:"folder_summary"` } @@ -596,6 +597,24 @@ func (s *GetFileInfoService) Get(c *gin.Context) (*FileResponse, error) { m := manager.NewFileManager(dep, user) defer m.Recycle() + if s.ID != "" && s.Uri == "" { + fileId, err := dep.HashIDEncoder().Decode(s.ID, hashid.FileID) + if err != nil { + return nil, serializer.NewError(serializer.CodeParamErr, "unknown file id", err) + } + + file, err := m.TraverseFile(c, fileId) + if err != nil { + return nil, fmt.Errorf("failed to traverse file: %w", err) + } + + s.Uri = file.Uri(true).String() + } + + if s.Uri == "" { + return nil, serializer.NewError(serializer.CodeParamErr, "uri is required", nil) + } + uri, err := fs.NewUriFromString(s.Uri) if err != nil { return nil, serializer.NewError(serializer.CodeParamErr, "unknown uri", err)