feat(profile): options to select why kind of share links to show in user's profile (#2453)

pull/2481/merge
Aaron Liu 2025-08-12 09:52:47 +08:00
parent bb3db2e326
commit b0057fe92f
7 changed files with 101 additions and 65 deletions

2
assets

@ -1 +1 @@
Subproject commit 8b2c8a7bdbde43a1f95ddaa4555e4304215c1e7c Subproject commit eb2cfac37d73e5bd3000eb66a3a0062509efe122

View File

@ -16,8 +16,11 @@ type (
Language string `json:"email_language,omitempty"` Language string `json:"email_language,omitempty"`
DisableViewSync bool `json:"disable_view_sync,omitempty"` DisableViewSync bool `json:"disable_view_sync,omitempty"`
FsViewMap map[string]ExplorerView `json:"fs_view_map,omitempty"` FsViewMap map[string]ExplorerView `json:"fs_view_map,omitempty"`
ShareLinksInProfile ShareLinksInProfileLevel `json:"share_links_in_profile,omitempty"`
} }
ShareLinksInProfileLevel string
PinedFile struct { PinedFile struct {
Uri string `json:"uri"` Uri string `json:"uri"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
@ -334,3 +337,9 @@ const (
CustomPropsTypeLink = "link" CustomPropsTypeLink = "link"
CustomPropsTypeRating = "rating" CustomPropsTypeRating = "rating"
) )
const (
ProfilePublicShareOnly = ShareLinksInProfileLevel("")
ProfileAllShare = ShareLinksInProfileLevel("all_share")
ProfileHideShare = ShareLinksInProfileLevel("hide_share")
)

View File

@ -278,6 +278,7 @@ type Share struct {
Downloaded int `json:"downloaded,omitempty"` Downloaded int `json:"downloaded,omitempty"`
Expires *time.Time `json:"expires,omitempty"` Expires *time.Time `json:"expires,omitempty"`
Unlocked bool `json:"unlocked"` Unlocked bool `json:"unlocked"`
PasswordProtected bool `json:"password_protected,omitempty"`
SourceType *types.FileType `json:"source_type,omitempty"` SourceType *types.FileType `json:"source_type,omitempty"`
Owner user.User `json:"owner"` Owner user.User `json:"owner"`
CreatedAt time.Time `json:"created_at,omitempty"` CreatedAt time.Time `json:"created_at,omitempty"`
@ -306,10 +307,11 @@ func BuildShare(s *ent.Share, base *url.URL, hasher hashid.Encoder, requester *e
Unlocked: unlocked, Unlocked: unlocked,
Owner: user.BuildUserRedacted(owner, redactLevel, hasher), Owner: user.BuildUserRedacted(owner, redactLevel, hasher),
Expired: inventory.IsShareExpired(s) != nil || expired, Expired: inventory.IsShareExpired(s) != nil || expired,
Url: BuildShareLink(s, hasher, base), Url: BuildShareLink(s, hasher, base, unlocked),
CreatedAt: s.CreatedAt, CreatedAt: s.CreatedAt,
Visited: s.Views, Visited: s.Views,
SourceType: util.ToPtr(t), SourceType: util.ToPtr(t),
PasswordProtected: s.Password != "",
} }
if unlocked { if unlocked {
@ -436,9 +438,12 @@ func BuildEntity(extendedInfo *fs.FileExtendedInfo, e fs.Entity, hasher hashid.E
} }
} }
func BuildShareLink(s *ent.Share, hasher hashid.Encoder, base *url.URL) string { func BuildShareLink(s *ent.Share, hasher hashid.Encoder, base *url.URL, unlocked bool) string {
shareId := hashid.EncodeShareID(hasher, s.ID) shareId := hashid.EncodeShareID(hasher, s.ID)
if unlocked {
return routes.MasterShareUrl(base, shareId, s.Password).String() return routes.MasterShareUrl(base, shareId, s.Password).String()
}
return routes.MasterShareUrl(base, shareId, "").String()
} }
func BuildStoragePolicy(sp *ent.StoragePolicy, hasher hashid.Encoder) *StoragePolicy { func BuildStoragePolicy(sp *ent.StoragePolicy, hasher hashid.Encoder) *StoragePolicy {

View File

@ -66,7 +66,7 @@ func (service *ShareCreateService) Upsert(c *gin.Context, existed int) (string,
} }
base := dep.SettingProvider().SiteURL(c) base := dep.SettingProvider().SiteURL(c)
return explorer.BuildShareLink(share, dep.HashIDEncoder(), base), nil return explorer.BuildShareLink(share, dep.HashIDEncoder(), base, true), nil
} }
func DeleteShare(c *gin.Context, shareId int) error { func DeleteShare(c *gin.Context, shareId int) error {

View File

@ -137,6 +137,16 @@ func (s *ListShareService) ListInUserProfile(c *gin.Context, uid int) (*ListShar
hasher := dep.HashIDEncoder() hasher := dep.HashIDEncoder()
shareClient := dep.ShareClient() shareClient := dep.ShareClient()
targetUser, err := dep.UserClient().GetActiveByID(c, uid)
if err != nil {
return nil, serializer.NewError(serializer.CodeDBError, "Failed to get user", err)
}
if targetUser.Settings != nil && targetUser.Settings.ShareLinksInProfile == types.ProfileHideShare {
return nil, serializer.NewError(serializer.CodeParamErr, "User has disabled share links in profile", nil)
}
publicOnly := targetUser.Settings == nil || targetUser.Settings.ShareLinksInProfile == types.ProfilePublicShareOnly
args := &inventory.ListShareArgs{ args := &inventory.ListShareArgs{
PaginationArgs: &inventory.PaginationArgs{ PaginationArgs: &inventory.PaginationArgs{
UseCursorPagination: true, UseCursorPagination: true,
@ -146,7 +156,7 @@ func (s *ListShareService) ListInUserProfile(c *gin.Context, uid int) (*ListShar
OrderBy: s.OrderBy, OrderBy: s.OrderBy,
}, },
UserID: uid, UserID: uid,
PublicOnly: true, PublicOnly: publicOnly,
} }
ctx := context.WithValue(c, inventory.LoadShareUser{}, true) ctx := context.WithValue(c, inventory.LoadShareUser{}, true)

View File

@ -29,6 +29,7 @@ type UserSettings struct {
TwoFAEnabled bool `json:"two_fa_enabled"` TwoFAEnabled bool `json:"two_fa_enabled"`
Passkeys []Passkey `json:"passkeys,omitempty"` Passkeys []Passkey `json:"passkeys,omitempty"`
DisableViewSync bool `json:"disable_view_sync"` DisableViewSync bool `json:"disable_view_sync"`
ShareLinksInProfile string `json:"share_links_in_profile"`
} }
func BuildUserSettings(u *ent.User, passkeys []*ent.Passkey, parser *uaparser.Parser) *UserSettings { func BuildUserSettings(u *ent.User, passkeys []*ent.Passkey, parser *uaparser.Parser) *UserSettings {
@ -42,6 +43,7 @@ func BuildUserSettings(u *ent.User, passkeys []*ent.Passkey, parser *uaparser.Pa
return BuildPasskey(item) return BuildPasskey(item)
}), }),
DisableViewSync: u.Settings.DisableViewSync, DisableViewSync: u.Settings.DisableViewSync,
ShareLinksInProfile: string(u.Settings.ShareLinksInProfile),
} }
} }
@ -109,6 +111,7 @@ type User struct {
Pined []types.PinedFile `json:"pined,omitempty"` Pined []types.PinedFile `json:"pined,omitempty"`
Language string `json:"language,omitempty"` Language string `json:"language,omitempty"`
DisableViewSync bool `json:"disable_view_sync,omitempty"` DisableViewSync bool `json:"disable_view_sync,omitempty"`
ShareLinksInProfile types.ShareLinksInProfileLevel `json:"share_links_in_profile,omitempty"`
} }
type Group struct { type Group struct {
@ -165,6 +168,7 @@ func BuildUser(user *ent.User, idEncoder hashid.Encoder) User {
Pined: user.Settings.Pined, Pined: user.Settings.Pined,
Language: user.Settings.Language, Language: user.Settings.Language,
DisableViewSync: user.Settings.DisableViewSync, DisableViewSync: user.Settings.DisableViewSync,
ShareLinksInProfile: user.Settings.ShareLinksInProfile,
} }
} }
@ -197,6 +201,7 @@ func BuildUserRedacted(u *ent.User, level int, idEncoder hashid.Encoder) User {
Nickname: userRaw.Nickname, Nickname: userRaw.Nickname,
Avatar: userRaw.Avatar, Avatar: userRaw.Avatar,
CreatedAt: userRaw.CreatedAt, CreatedAt: userRaw.CreatedAt,
ShareLinksInProfile: userRaw.ShareLinksInProfile,
} }
if userRaw.Group != nil { if userRaw.Group != nil {

View File

@ -14,6 +14,7 @@ import (
"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"
"github.com/cloudreve/Cloudreve/v4/inventory" "github.com/cloudreve/Cloudreve/v4/inventory"
"github.com/cloudreve/Cloudreve/v4/inventory/types"
"github.com/cloudreve/Cloudreve/v4/pkg/hashid" "github.com/cloudreve/Cloudreve/v4/pkg/hashid"
"github.com/cloudreve/Cloudreve/v4/pkg/request" "github.com/cloudreve/Cloudreve/v4/pkg/request"
"github.com/cloudreve/Cloudreve/v4/pkg/serializer" "github.com/cloudreve/Cloudreve/v4/pkg/serializer"
@ -221,6 +222,7 @@ type (
TwoFAEnabled *bool `json:"two_fa_enabled" binding:"omitempty"` TwoFAEnabled *bool `json:"two_fa_enabled" binding:"omitempty"`
TwoFACode *string `json:"two_fa_code" binding:"omitempty"` TwoFACode *string `json:"two_fa_code" binding:"omitempty"`
DisableViewSync *bool `json:"disable_view_sync" binding:"omitempty"` DisableViewSync *bool `json:"disable_view_sync" binding:"omitempty"`
ShareLinksInProfile *string `json:"share_links_in_profile" binding:"omitempty"`
} }
PatchUserSettingParamsCtx struct{} PatchUserSettingParamsCtx struct{}
) )
@ -267,6 +269,11 @@ func (s *PatchUserSetting) Patch(c *gin.Context) error {
saveSetting = true saveSetting = true
} }
if s.ShareLinksInProfile != nil {
u.Settings.ShareLinksInProfile = types.ShareLinksInProfileLevel(*s.ShareLinksInProfile)
saveSetting = true
}
if s.CurrentPassword != nil && s.NewPassword != nil { if s.CurrentPassword != nil && s.NewPassword != nil {
if err := inventory.CheckPassword(u, *s.CurrentPassword); err != nil { if err := inventory.CheckPassword(u, *s.CurrentPassword); err != nil {
return serializer.NewError(serializer.CodeIncorrectPassword, "Incorrect password", err) return serializer.NewError(serializer.CodeIncorrectPassword, "Incorrect password", err)