chore: 初始化 Cloudreve 源码仓库
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
package share
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v4/application/dependency"
|
||||
"github.com/cloudreve/Cloudreve/v4/ent"
|
||||
"github.com/cloudreve/Cloudreve/v4/inventory"
|
||||
"github.com/cloudreve/Cloudreve/v4/inventory/types"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/manager"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/hashid"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
|
||||
"github.com/cloudreve/Cloudreve/v4/service/explorer"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type (
|
||||
// ShareCreateService 创建新分享服务
|
||||
ShareCreateService struct {
|
||||
Uri string `json:"uri" binding:"required"`
|
||||
IsPrivate bool `json:"is_private"`
|
||||
Password string `json:"password" binding:"omitempty,max=32,alphanum"`
|
||||
RemainDownloads int `json:"downloads"`
|
||||
Expire int `json:"expire"`
|
||||
ShareView bool `json:"share_view"`
|
||||
ShowReadMe bool `json:"show_readme"`
|
||||
}
|
||||
ShareCreateParamCtx struct{}
|
||||
|
||||
BatchDeleteShareService struct {
|
||||
ShareIDs []string `json:"ids" binding:"required"`
|
||||
}
|
||||
BatchDeleteParamCtx struct{}
|
||||
)
|
||||
|
||||
func (service *BatchDeleteShareService) Delete(c *gin.Context) error {
|
||||
dep := dependency.FromContext(c)
|
||||
uid := inventory.UserIDFromContext(c)
|
||||
shareClient := dep.ShareClient()
|
||||
|
||||
var ids []int
|
||||
|
||||
for _, v := range service.ShareIDs {
|
||||
id, err := dep.HashIDEncoder().Decode(v, hashid.ShareID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode hash id %q: %w", v, err)
|
||||
}
|
||||
|
||||
ids = append(ids, id)
|
||||
}
|
||||
|
||||
if err := shareClient.DeleteBatchByUserID(c, uid, ids); err != nil {
|
||||
return serializer.NewError(serializer.CodeDBError, "Failed to delete shares", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upsert 创建或更新分享
|
||||
func (service *ShareCreateService) Upsert(c *gin.Context, existed int) (string, error) {
|
||||
dep := dependency.FromContext(c)
|
||||
user := inventory.UserFromContext(c)
|
||||
m := manager.NewFileManager(dep, user)
|
||||
defer m.Recycle()
|
||||
|
||||
// Check group permission for creating share link
|
||||
if !user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionShare)) {
|
||||
return "", serializer.NewError(serializer.CodeGroupNotAllowed, "Group permission denied", nil)
|
||||
}
|
||||
|
||||
uri, err := fs.NewUriFromString(service.Uri)
|
||||
if err != nil {
|
||||
return "", serializer.NewError(serializer.CodeParamErr, "unknown uri", err)
|
||||
}
|
||||
|
||||
var expires *time.Time
|
||||
if service.Expire > 0 {
|
||||
expires = new(time.Time)
|
||||
*expires = time.Now().Add(time.Duration(service.Expire) * time.Second)
|
||||
}
|
||||
|
||||
share, err := m.CreateOrUpdateShare(c, uri, &manager.CreateShareArgs{
|
||||
IsPrivate: service.IsPrivate,
|
||||
Password: service.Password,
|
||||
RemainDownloads: service.RemainDownloads,
|
||||
Expire: expires,
|
||||
ExistedShareID: existed,
|
||||
ShareView: service.ShareView,
|
||||
ShowReadMe: service.ShowReadMe,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
base := dep.SettingProvider().SiteURL(c)
|
||||
return explorer.BuildShareLink(share, dep.HashIDEncoder(), base, true), nil
|
||||
}
|
||||
|
||||
func DeleteShare(c *gin.Context, shareId int) error {
|
||||
dep := dependency.FromContext(c)
|
||||
user := inventory.UserFromContext(c)
|
||||
shareClient := dep.ShareClient()
|
||||
|
||||
ctx := context.WithValue(c, inventory.LoadShareFile{}, true)
|
||||
var (
|
||||
share *ent.Share
|
||||
err error
|
||||
)
|
||||
if user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionIsAdmin)) {
|
||||
share, err = shareClient.GetByID(ctx, shareId)
|
||||
} else {
|
||||
share, err = shareClient.GetByIDUser(ctx, shareId, user.ID)
|
||||
}
|
||||
if err != nil {
|
||||
return serializer.NewError(serializer.CodeNotFound, "share not found", err)
|
||||
}
|
||||
|
||||
if err := shareClient.Delete(c, share.ID); err != nil {
|
||||
return serializer.NewError(serializer.CodeDBError, "Failed to delete share", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package share
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v4/ent"
|
||||
"github.com/cloudreve/Cloudreve/v4/inventory"
|
||||
"github.com/cloudreve/Cloudreve/v4/inventory/types"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs/dbfs"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/hashid"
|
||||
"github.com/cloudreve/Cloudreve/v4/service/explorer"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type ListShareResponse struct {
|
||||
Shares []explorer.Share `json:"shares"`
|
||||
Pagination *inventory.PaginationResults `json:"pagination"`
|
||||
}
|
||||
|
||||
func BuildListShareResponse(ctx context.Context, res *inventory.ListShareResult, hasher hashid.Encoder, base *url.URL, requester *ent.User, unlocked bool) *ListShareResponse {
|
||||
var infos []explorer.Share
|
||||
for _, share := range res.Shares {
|
||||
expired := inventory.IsValidShare(share) != nil
|
||||
shareName := share.Edges.File.Name
|
||||
if share.Edges.File.FileChildren == 0 && len(share.Edges.File.Edges.Metadata) >= 0 {
|
||||
// For files in trash bin, read the real name from metadata
|
||||
restoreUri, found := lo.Find(share.Edges.File.Edges.Metadata, func(m *ent.Metadata) bool {
|
||||
return m.Name == dbfs.MetadataRestoreUri
|
||||
})
|
||||
if found {
|
||||
uri, err := fs.NewUriFromString(restoreUri.Value)
|
||||
if err == nil {
|
||||
shareName = uri.Name()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
infos = append(infos, *explorer.BuildShare(ctx, share, base, hasher, requester, share.Edges.User, shareName,
|
||||
types.FileType(share.Edges.File.Type), unlocked, expired))
|
||||
}
|
||||
|
||||
return &ListShareResponse{
|
||||
Shares: infos,
|
||||
Pagination: res.PaginationResults,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package share
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v4/application/dependency"
|
||||
"github.com/cloudreve/Cloudreve/v4/ent"
|
||||
"github.com/cloudreve/Cloudreve/v4/inventory"
|
||||
"github.com/cloudreve/Cloudreve/v4/inventory/types"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/cluster/routes"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/manager"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/hashid"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
|
||||
"github.com/cloudreve/Cloudreve/v4/service/explorer"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type (
|
||||
ShortLinkRedirectService struct {
|
||||
ID string `uri:"id" binding:"required"`
|
||||
Password string `uri:"password"`
|
||||
}
|
||||
ShortLinkRedirectParamCtx struct{}
|
||||
)
|
||||
|
||||
func (s *ShortLinkRedirectService) RedirectTo(c *gin.Context) string {
|
||||
shareLongUrl := routes.MasterShareLongUrl(s.ID, s.Password)
|
||||
|
||||
shortLinkQuery := c.Request.URL.Query() // Query in ShortLink, adapt to Cloudreve V3
|
||||
shareLongUrlQuery := shareLongUrl.Query()
|
||||
|
||||
userSpecifiedPath := shortLinkQuery.Get("path")
|
||||
if userSpecifiedPath != "" {
|
||||
masterPath := shareLongUrlQuery.Get("path")
|
||||
masterPath += "/" + strings.TrimPrefix(userSpecifiedPath, "/")
|
||||
|
||||
shareLongUrlQuery.Set("path", masterPath)
|
||||
}
|
||||
|
||||
shortLinkQuery.Del("path") // 防止用户指定的 Path 就是空字符串
|
||||
for k, vals := range shortLinkQuery {
|
||||
shareLongUrlQuery[k] = append(shareLongUrlQuery[k], vals...)
|
||||
}
|
||||
|
||||
shareLongUrl.RawQuery = shareLongUrlQuery.Encode()
|
||||
return shareLongUrl.String()
|
||||
}
|
||||
|
||||
type (
|
||||
ShareInfoService struct {
|
||||
Password string `form:"password"`
|
||||
CountViews bool `form:"count_views"`
|
||||
OwnerExtended bool `form:"owner_extended"`
|
||||
}
|
||||
ShareInfoParamCtx struct{}
|
||||
)
|
||||
|
||||
func (s *ShareInfoService) Get(c *gin.Context) (*explorer.Share, error) {
|
||||
dep := dependency.FromContext(c)
|
||||
u := inventory.UserFromContext(c)
|
||||
shareClient := dep.ShareClient()
|
||||
|
||||
ctx := context.WithValue(c, inventory.LoadShareUser{}, true)
|
||||
ctx = context.WithValue(ctx, inventory.LoadShareFile{}, true)
|
||||
share, err := shareClient.GetByID(ctx, hashid.FromContext(c))
|
||||
if err != nil {
|
||||
if ent.IsNotFound(err) {
|
||||
return nil, serializer.NewError(serializer.CodeNotFound, "Share not found", nil)
|
||||
}
|
||||
return nil, serializer.NewError(serializer.CodeDBError, "Failed to get share", err)
|
||||
}
|
||||
|
||||
if err := inventory.IsValidShare(share); err != nil {
|
||||
return nil, serializer.NewError(serializer.CodeNotFound, "Share link expired", err)
|
||||
}
|
||||
|
||||
if s.CountViews {
|
||||
_ = shareClient.Viewed(c, share)
|
||||
}
|
||||
|
||||
unlocked := true
|
||||
// Share requires password
|
||||
if share.Password != "" && s.Password != share.Password && share.Edges.User.ID != u.ID {
|
||||
unlocked = false
|
||||
}
|
||||
|
||||
base := dep.SettingProvider().SiteURL(c)
|
||||
res := explorer.BuildShare(c, share, base, dep.HashIDEncoder(), u, share.Edges.User, share.Edges.File.Name,
|
||||
types.FileType(share.Edges.File.Type), unlocked, false)
|
||||
|
||||
if s.OwnerExtended && share.Edges.User.ID == u.ID {
|
||||
// Add more information about the shared file
|
||||
m := manager.NewFileManager(dep, u)
|
||||
defer m.Recycle()
|
||||
|
||||
shareUri, err := fs.NewUriFromString(fs.NewShareUri(res.ID, s.Password))
|
||||
if err != nil {
|
||||
return nil, serializer.NewError(serializer.CodeInternalSetting, "Invalid share url", err)
|
||||
}
|
||||
|
||||
root, err := m.Get(c, shareUri)
|
||||
if err != nil {
|
||||
return nil, serializer.NewError(serializer.CodeNotFound, "File not found", err)
|
||||
}
|
||||
|
||||
res.SourceUri = root.Uri(true).String()
|
||||
}
|
||||
|
||||
return res, nil
|
||||
|
||||
}
|
||||
|
||||
type (
|
||||
ListShareService struct {
|
||||
PageSize int `form:"page_size" binding:"required,min=10,max=100"`
|
||||
OrderBy string `uri:"order_by" form:"order_by" json:"order_by"`
|
||||
OrderDirection string `uri:"order_direction" form:"order_direction" json:"order_direction"`
|
||||
NextPageToken string `form:"next_page_token"`
|
||||
}
|
||||
ListShareParamCtx struct{}
|
||||
)
|
||||
|
||||
func (s *ListShareService) List(c *gin.Context) (*ListShareResponse, error) {
|
||||
dep := dependency.FromContext(c)
|
||||
user := inventory.UserFromContext(c)
|
||||
hasher := dep.HashIDEncoder()
|
||||
shareClient := dep.ShareClient()
|
||||
|
||||
args := &inventory.ListShareArgs{
|
||||
PaginationArgs: &inventory.PaginationArgs{
|
||||
UseCursorPagination: true,
|
||||
PageToken: s.NextPageToken,
|
||||
PageSize: s.PageSize,
|
||||
Order: inventory.OrderDirection(s.OrderDirection),
|
||||
OrderBy: s.OrderBy,
|
||||
},
|
||||
UserID: user.ID,
|
||||
}
|
||||
|
||||
ctx := context.WithValue(c, inventory.LoadShareUser{}, true)
|
||||
ctx = context.WithValue(ctx, inventory.LoadShareFile{}, true)
|
||||
ctx = context.WithValue(ctx, inventory.LoadFileMetadata{}, true)
|
||||
res, err := shareClient.List(ctx, args)
|
||||
if err != nil {
|
||||
return nil, serializer.NewError(serializer.CodeDBError, "Failed to list shares", err)
|
||||
}
|
||||
|
||||
base := dep.SettingProvider().SiteURL(ctx)
|
||||
return BuildListShareResponse(ctx, res, hasher, base, user, true), nil
|
||||
}
|
||||
|
||||
func (s *ListShareService) ListInUserProfile(c *gin.Context, uid int) (*ListShareResponse, error) {
|
||||
dep := dependency.FromContext(c)
|
||||
user := inventory.UserFromContext(c)
|
||||
hasher := dep.HashIDEncoder()
|
||||
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{
|
||||
PaginationArgs: &inventory.PaginationArgs{
|
||||
UseCursorPagination: true,
|
||||
PageToken: s.NextPageToken,
|
||||
PageSize: s.PageSize,
|
||||
Order: inventory.OrderDirection(s.OrderDirection),
|
||||
OrderBy: s.OrderBy,
|
||||
},
|
||||
UserID: uid,
|
||||
PublicOnly: publicOnly,
|
||||
}
|
||||
|
||||
ctx := context.WithValue(c, inventory.LoadShareUser{}, true)
|
||||
ctx = context.WithValue(ctx, inventory.LoadShareFile{}, true)
|
||||
ctx = context.WithValue(ctx, inventory.LoadFileMetadata{}, true)
|
||||
res, err := shareClient.List(ctx, args)
|
||||
if err != nil {
|
||||
return nil, serializer.NewError(serializer.CodeDBError, "Failed to list shares", err)
|
||||
}
|
||||
|
||||
base := dep.SettingProvider().SiteURL(ctx)
|
||||
return BuildListShareResponse(ctx, res, hasher, base, user, false), nil
|
||||
}
|
||||
Reference in New Issue
Block a user