feat(dbfs): set default share shortcut for new users

pull/2224/merge
Aaron Liu 2025-05-23 15:39:57 +08:00
parent 9f5ebe11b6
commit c6ee3e5dcd
7 changed files with 104 additions and 7 deletions

View File

@ -246,4 +246,5 @@ var DefaultSettings = map[string]string{
"qq_login": `0`, "qq_login": `0`,
"qq_login_config": `{"direct_sign_in":false}`, "qq_login_config": `{"direct_sign_in":false}`,
"license": "", "license": "",
"default_symbolics": "[]",
} }

View File

@ -31,6 +31,8 @@ var (
type ( type (
ShareClient interface { ShareClient interface {
TxOperator 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 returns the share with given id.
GetByID(ctx context.Context, id int) (*ent.Share, error) GetByID(ctx context.Context, id int) (*ent.Share, error)
// GetByIDUser returns the share with given id and user id. // GetByIDUser returns the share with given id and user id.
@ -67,6 +69,7 @@ type (
UserID int UserID int
FileID int FileID int
PublicOnly bool PublicOnly bool
ShareIDs []int
} }
ListShareResult struct { ListShareResult struct {
*PaginationResults *PaginationResults
@ -168,6 +171,15 @@ func (c *shareClient) GetByIDUser(ctx context.Context, id, uid int) (*ent.Share,
return s, nil 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 { func (c *shareClient) DeleteBatch(ctx context.Context, shareIds []int) error {
_, err := c.client.Share.Delete().Where(share.IDIn(shareIds...)).Exec(ctx) _, err := c.client.Share.Delete().Where(share.IDIn(shareIds...)).Exec(ctx)
return err return err
@ -332,6 +344,10 @@ func (c *shareClient) listQuery(args *ListShareArgs) *ent.ShareQuery {
query.Where(share.HasFileWith(file.ID(args.FileID))) query.Where(share.HasFileWith(file.ID(args.FileID)))
} }
if len(args.ShareIDs) > 0 {
query.Where(share.IDIn(args.ShareIDs...))
}
return query return query
} }

View File

@ -719,7 +719,7 @@ func (f *DBFS) getFileByPath(ctx context.Context, navigator Navigator, path *fs.
// initFs initializes the file system for the user. // initFs initializes the file system for the user.
func (f *DBFS) initFs(ctx context.Context, uid int) error { func (f *DBFS) initFs(ctx context.Context, uid int) error {
f.l.Info("Initialize database file system for user %q", f.user.Email) 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{ &inventory.CreateFolderParameters{
Owner: uid, Owner: uid,
Name: inventory.RootFolderName, 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) 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 return nil
} }

View File

@ -53,6 +53,7 @@ const (
MetadataSharedRedirect = MetadataSysPrefix + "shared_redirect" MetadataSharedRedirect = MetadataSysPrefix + "shared_redirect"
MetadataRestoreUri = MetadataSysPrefix + "restore_uri" MetadataRestoreUri = MetadataSysPrefix + "restore_uri"
MetadataExpectedCollectTime = MetadataSysPrefix + "expected_collect_time" MetadataExpectedCollectTime = MetadataSysPrefix + "expected_collect_time"
MetadataSharedOwner = MetadataSysPrefix + "shared_owner"
ThumbMetadataPrefix = "thumb:" ThumbMetadataPrefix = "thumb:"
ThumbDisabledKey = ThumbMetadataPrefix + "disabled" ThumbDisabledKey = ThumbMetadataPrefix + "disabled"

View File

@ -184,6 +184,8 @@ type (
AvatarProcess(ctx context.Context) *AvatarProcess AvatarProcess(ctx context.Context) *AvatarProcess
// UseFirstSiteUrl returns the first site URL. // UseFirstSiteUrl returns the first site URL.
AllSiteURLs(ctx context.Context) []*url.URL AllSiteURLs(ctx context.Context) []*url.URL
// DefaultSymbolics returns the default symbolics for new users.
DefaultSymbolics(ctx context.Context) []int
} }
UseFirstSiteUrlCtxKey = struct{} 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 { func (s *settingProvider) License(ctx context.Context) string {
return s.getString(ctx, "license", "") return s.getString(ctx, "license", "")
} }

View File

@ -3,6 +3,7 @@ package admin
import ( import (
"context" "context"
"strconv" "strconv"
"strings"
"github.com/cloudreve/Cloudreve/v4/application/dependency" "github.com/cloudreve/Cloudreve/v4/application/dependency"
"github.com/cloudreve/Cloudreve/v4/ent" "github.com/cloudreve/Cloudreve/v4/ent"
@ -17,6 +18,7 @@ import (
const ( const (
shareUserIDCondition = "share_user_id" shareUserIDCondition = "share_user_id"
shareFileIDCondition = "share_file_id" shareFileIDCondition = "share_file_id"
shareIDCondition = "share_id"
) )
func (s *AdminListService) Shares(c *gin.Context) (*ListShareResponse, error) { 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() hasher := dep.HashIDEncoder()
var ( var (
err error err error
userID int userID int
fileID int fileID int
shareIDs []int
) )
if s.Conditions[shareUserIDCondition] != "" { 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(c, inventory.LoadShareFile{}, true)
ctx = context.WithValue(ctx, inventory.LoadShareUser{}, true) ctx = context.WithValue(ctx, inventory.LoadShareUser{}, true)
@ -54,8 +69,9 @@ func (s *AdminListService) Shares(c *gin.Context) (*ListShareResponse, error) {
OrderBy: s.OrderBy, OrderBy: s.OrderBy,
Order: inventory.OrderDirection(s.OrderDirection), Order: inventory.OrderDirection(s.OrderDirection),
}, },
UserID: userID, UserID: userID,
FileID: fileID, FileID: fileID,
ShareIDs: shareIDs,
}) })
if err != nil { if err != nil {

View File

@ -261,7 +261,6 @@ func (service *WopiService) PutContent(c *gin.Context, isPutRelative bool) error
if isPutRelative { if isPutRelative {
c.JSON(http.StatusOK, PutRelativeResponse{ c.JSON(http.StatusOK, PutRelativeResponse{
Name: res.Name, Name: res.Name,
Url: "http://docker.host.internal:5212/explorer/viewer?uri=",
}) })
} }
return nil return nil