From c6ee3e5dcd6c5463f7c6d310a8826cc6dbf9c95d Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Fri, 23 May 2025 15:39:57 +0800 Subject: [PATCH] feat(dbfs): set default share shortcut for new users --- inventory/setting.go | 1 + inventory/share.go | 16 ++++++++++ pkg/filemanager/fs/dbfs/dbfs.go | 54 ++++++++++++++++++++++++++++++++- pkg/filemanager/fs/dbfs/file.go | 1 + pkg/setting/provider.go | 12 ++++++++ service/admin/share.go | 26 +++++++++++++--- service/explorer/viewer.go | 1 - 7 files changed, 104 insertions(+), 7 deletions(-) diff --git a/inventory/setting.go b/inventory/setting.go index 8d48439..1fe7a8e 100644 --- a/inventory/setting.go +++ b/inventory/setting.go @@ -246,4 +246,5 @@ var DefaultSettings = map[string]string{ "qq_login": `0`, "qq_login_config": `{"direct_sign_in":false}`, "license": "", + "default_symbolics": "[]", } diff --git a/inventory/share.go b/inventory/share.go index a43c08b..a2e0f96 100644 --- a/inventory/share.go +++ b/inventory/share.go @@ -31,6 +31,8 @@ var ( type ( ShareClient interface { TxOperator + // GetByIDs returns the shares with given ids. + GetByIDs(ctx context.Context, ids []int) ([]*ent.Share, error) // GetByID returns the share with given id. GetByID(ctx context.Context, id int) (*ent.Share, error) // GetByIDUser returns the share with given id and user id. @@ -67,6 +69,7 @@ type ( UserID int FileID int PublicOnly bool + ShareIDs []int } ListShareResult struct { *PaginationResults @@ -168,6 +171,15 @@ func (c *shareClient) GetByIDUser(ctx context.Context, id, uid int) (*ent.Share, return s, nil } +func (c *shareClient) GetByIDs(ctx context.Context, ids []int) ([]*ent.Share, error) { + s, err := withShareEagerLoading(ctx, c.client.Share.Query().Where(share.IDIn(ids...))).All(ctx) + if err != nil { + return nil, fmt.Errorf("failed to query shares %v: %w", ids, err) + } + + return s, nil +} + func (c *shareClient) DeleteBatch(ctx context.Context, shareIds []int) error { _, err := c.client.Share.Delete().Where(share.IDIn(shareIds...)).Exec(ctx) return err @@ -332,6 +344,10 @@ func (c *shareClient) listQuery(args *ListShareArgs) *ent.ShareQuery { query.Where(share.HasFileWith(file.ID(args.FileID))) } + if len(args.ShareIDs) > 0 { + query.Where(share.IDIn(args.ShareIDs...)) + } + return query } diff --git a/pkg/filemanager/fs/dbfs/dbfs.go b/pkg/filemanager/fs/dbfs/dbfs.go index 590d518..12548a3 100644 --- a/pkg/filemanager/fs/dbfs/dbfs.go +++ b/pkg/filemanager/fs/dbfs/dbfs.go @@ -719,7 +719,7 @@ func (f *DBFS) getFileByPath(ctx context.Context, navigator Navigator, path *fs. // initFs initializes the file system for the user. func (f *DBFS) initFs(ctx context.Context, uid int) error { f.l.Info("Initialize database file system for user %q", f.user.Email) - _, err := f.fileClient.CreateFolder(ctx, nil, + parent, err := f.fileClient.CreateFolder(ctx, nil, &inventory.CreateFolderParameters{ Owner: uid, Name: inventory.RootFolderName, @@ -728,6 +728,58 @@ func (f *DBFS) initFs(ctx context.Context, uid int) error { return fmt.Errorf("failed to create root folder: %w", err) } + // Create default symbolics + symbolics := f.settingClient.DefaultSymbolics(ctx) + if len(symbolics) == 0 { + return nil + } + + user, err := f.userClient.GetLoginUserByID(ctx, uid) + if err != nil { + return fmt.Errorf("failed to get user: %w", err) + } + + parent.SetOwner(user) + + ctx = context.WithValue(ctx, inventory.LoadShareFile{}, true) + ctx = context.WithValue(ctx, inventory.LoadShareUser{}, true) + shares, err := f.shareClient.GetByIDs(ctx, symbolics) + if err != nil { + return fmt.Errorf("failed to get shares: %w", err) + } + + for _, share := range shares { + if share.Edges.File == nil || share.Edges.User == nil { + continue + } + + shareOwner := hashid.EncodeUserID(f.hasher, share.Edges.User.ID) + shareUri := fs.NewShareUri(hashid.EncodeShareID(f.hasher, share.ID), share.Password) + shareUriParsed, err := fs.NewUriFromString(shareUri) + if err != nil { + continue + } + + if share.Edges.File.Type == int(types.FileTypeFile) { + shareUriParsed.Join(share.Edges.File.Name) + } + + parentFile := newFile(nil, parent) + parentFile.OwnerModel = user + if _, err := f.createFile(ctx, parentFile, share.Edges.File.Name, types.FileType(share.Edges.File.Type), &dbfsOption{ + FsOption: &fs.FsOption{ + Metadata: map[string]string{ + MetadataSharedRedirect: shareUriParsed.String(), + MetadataSharedOwner: shareOwner, + }, + }, + errOnConflict: true, + isSymbolicLink: true, + }); err != nil { + f.l.Warning("Failed to create default symbolic link %q for user %q: %s", shareUriParsed, uid, err) + } + } + return nil } diff --git a/pkg/filemanager/fs/dbfs/file.go b/pkg/filemanager/fs/dbfs/file.go index a6ea222..0a1950d 100644 --- a/pkg/filemanager/fs/dbfs/file.go +++ b/pkg/filemanager/fs/dbfs/file.go @@ -53,6 +53,7 @@ const ( MetadataSharedRedirect = MetadataSysPrefix + "shared_redirect" MetadataRestoreUri = MetadataSysPrefix + "restore_uri" MetadataExpectedCollectTime = MetadataSysPrefix + "expected_collect_time" + MetadataSharedOwner = MetadataSysPrefix + "shared_owner" ThumbMetadataPrefix = "thumb:" ThumbDisabledKey = ThumbMetadataPrefix + "disabled" diff --git a/pkg/setting/provider.go b/pkg/setting/provider.go index 4cc4aa0..6c871b9 100644 --- a/pkg/setting/provider.go +++ b/pkg/setting/provider.go @@ -184,6 +184,8 @@ type ( AvatarProcess(ctx context.Context) *AvatarProcess // UseFirstSiteUrl returns the first site URL. AllSiteURLs(ctx context.Context) []*url.URL + // DefaultSymbolics returns the default symbolics for new users. + DefaultSymbolics(ctx context.Context) []int } UseFirstSiteUrlCtxKey = struct{} ) @@ -211,6 +213,16 @@ type ( } ) +func (s *settingProvider) DefaultSymbolics(ctx context.Context) []int { + raw := s.getString(ctx, "default_symbolics", "[]") + var symbolics []int + if err := json.Unmarshal([]byte(raw), &symbolics); err != nil { + return []int{} + } + + return symbolics +} + func (s *settingProvider) License(ctx context.Context) string { return s.getString(ctx, "license", "") } diff --git a/service/admin/share.go b/service/admin/share.go index 5a5d31b..4b36838 100644 --- a/service/admin/share.go +++ b/service/admin/share.go @@ -3,6 +3,7 @@ package admin import ( "context" "strconv" + "strings" "github.com/cloudreve/Cloudreve/v4/application/dependency" "github.com/cloudreve/Cloudreve/v4/ent" @@ -17,6 +18,7 @@ import ( const ( shareUserIDCondition = "share_user_id" shareFileIDCondition = "share_file_id" + shareIDCondition = "share_id" ) func (s *AdminListService) Shares(c *gin.Context) (*ListShareResponse, error) { @@ -25,9 +27,10 @@ func (s *AdminListService) Shares(c *gin.Context) (*ListShareResponse, error) { hasher := dep.HashIDEncoder() var ( - err error - userID int - fileID int + err error + userID int + fileID int + shareIDs []int ) if s.Conditions[shareUserIDCondition] != "" { @@ -44,6 +47,18 @@ func (s *AdminListService) Shares(c *gin.Context) (*ListShareResponse, error) { } } + if s.Conditions[shareIDCondition] != "" { + shareIdStrs := strings.Split(s.Conditions[shareIDCondition], ",") + for _, shareIdStr := range shareIdStrs { + shareID, err := strconv.Atoi(shareIdStr) + if err != nil { + return nil, serializer.NewError(serializer.CodeParamErr, "Invalid share ID", err) + } + + shareIDs = append(shareIDs, shareID) + } + } + ctx := context.WithValue(c, inventory.LoadShareFile{}, true) ctx = context.WithValue(ctx, inventory.LoadShareUser{}, true) @@ -54,8 +69,9 @@ func (s *AdminListService) Shares(c *gin.Context) (*ListShareResponse, error) { OrderBy: s.OrderBy, Order: inventory.OrderDirection(s.OrderDirection), }, - UserID: userID, - FileID: fileID, + UserID: userID, + FileID: fileID, + ShareIDs: shareIDs, }) if err != nil { diff --git a/service/explorer/viewer.go b/service/explorer/viewer.go index 3385476..d8ebc88 100644 --- a/service/explorer/viewer.go +++ b/service/explorer/viewer.go @@ -261,7 +261,6 @@ func (service *WopiService) PutContent(c *gin.Context, isPutRelative bool) error if isPutRelative { c.JSON(http.StatusOK, PutRelativeResponse{ Name: res.Name, - Url: "http://docker.host.internal:5212/explorer/viewer?uri=", }) } return nil