mirror of https://github.com/cloudreve/Cloudreve
feat(explorer): manage created direct links / option to enable unique redirected direct links
parent
2500ebc6a4
commit
dc611bcb0d
2
assets
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit 0fa57541d5b1018251674ef01a7e799f6899564e
|
||||
Subproject commit 672b00192cb89575b56805d338eb0fdc0ca7ed22
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"github.com/cloudreve/Cloudreve/v4/ent"
|
||||
"github.com/cloudreve/Cloudreve/v4/ent/directlink"
|
||||
"github.com/cloudreve/Cloudreve/v4/ent/schema"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/conf"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/hashid"
|
||||
)
|
||||
|
@ -16,6 +17,8 @@ type (
|
|||
GetByNameID(ctx context.Context, id int, name string) (*ent.DirectLink, error)
|
||||
// GetByID get direct link by id
|
||||
GetByID(ctx context.Context, id int) (*ent.DirectLink, error)
|
||||
// Delete delete direct link by id
|
||||
Delete(ctx context.Context, id int) error
|
||||
}
|
||||
LoadDirectLinkFile struct{}
|
||||
)
|
||||
|
@ -60,6 +63,12 @@ func (d *directLinkClient) GetByNameID(ctx context.Context, id int, name string)
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func (d *directLinkClient) Delete(ctx context.Context, id int) error {
|
||||
ctx = schema.SkipSoftDelete(ctx)
|
||||
_, err := d.client.DirectLink.Delete().Where(directlink.ID(id)).Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
func withDirectLinkEagerLoading(ctx context.Context, q *ent.DirectLinkQuery) *ent.DirectLinkQuery {
|
||||
if v, ok := ctx.Value(LoadDirectLinkFile{}).(bool); ok && v {
|
||||
q.WithFile(func(m *ent.FileQuery) {
|
||||
|
|
|
@ -192,7 +192,7 @@ type FileClient interface {
|
|||
// UnlinkEntity unlinks an entity from a file
|
||||
UnlinkEntity(ctx context.Context, entity *ent.Entity, file *ent.File, owner *ent.User) (StorageDiff, error)
|
||||
// CreateDirectLink creates a direct link for a file
|
||||
CreateDirectLink(ctx context.Context, fileID int, name string, speed int) (*ent.DirectLink, error)
|
||||
CreateDirectLink(ctx context.Context, fileID int, name string, speed int, reuse bool) (*ent.DirectLink, error)
|
||||
// CountByTimeRange counts files created in a given time range
|
||||
CountByTimeRange(ctx context.Context, start, end *time.Time) (int, error)
|
||||
// CountEntityByTimeRange counts entities created in a given time range
|
||||
|
@ -322,13 +322,15 @@ func (f *fileClient) CountEntityByStoragePolicyID(ctx context.Context, storagePo
|
|||
return v[0].Count, v[0].Sum, nil
|
||||
}
|
||||
|
||||
func (f *fileClient) CreateDirectLink(ctx context.Context, file int, name string, speed int) (*ent.DirectLink, error) {
|
||||
// Find existed
|
||||
existed, err := f.client.DirectLink.
|
||||
Query().
|
||||
Where(directlink.FileID(file), directlink.Name(name), directlink.Speed(speed)).First(ctx)
|
||||
if err == nil {
|
||||
return existed, nil
|
||||
func (f *fileClient) CreateDirectLink(ctx context.Context, file int, name string, speed int, reuse bool) (*ent.DirectLink, error) {
|
||||
if reuse {
|
||||
// Find existed
|
||||
existed, err := f.client.DirectLink.
|
||||
Query().
|
||||
Where(directlink.FileID(file), directlink.Name(name), directlink.Speed(speed)).First(ctx)
|
||||
if err == nil {
|
||||
return existed, nil
|
||||
}
|
||||
}
|
||||
|
||||
return f.client.DirectLink.
|
||||
|
|
|
@ -206,6 +206,7 @@ const (
|
|||
GroupPermission_CommunityPlaceholder4
|
||||
GroupPermissionSetExplicitUser_placeholder
|
||||
GroupPermissionIgnoreFileOwnership // not used
|
||||
GroupPermissionUniqueRedirectDirectLink
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -384,6 +384,10 @@ func (f *DBFS) Get(ctx context.Context, path *fs.URI, opts ...fs.Option) (fs.Fil
|
|||
ctx = context.WithValue(ctx, inventory.LoadFileEntity{}, true)
|
||||
}
|
||||
|
||||
if o.extendedInfo {
|
||||
ctx = context.WithValue(ctx, inventory.LoadFileDirectLink{}, true)
|
||||
}
|
||||
|
||||
if o.loadFileShareIfOwned {
|
||||
ctx = context.WithValue(ctx, inventory.LoadFileShare{}, true)
|
||||
}
|
||||
|
@ -407,6 +411,11 @@ func (f *DBFS) Get(ctx context.Context, path *fs.URI, opts ...fs.Option) (fs.Fil
|
|||
StorageUsed: target.SizeUsed(),
|
||||
EntityStoragePolicies: make(map[int]*ent.StoragePolicy),
|
||||
}
|
||||
|
||||
if f.user.ID == target.OwnerID() {
|
||||
extendedInfo.DirectLinks = target.Model.Edges.DirectLinks
|
||||
}
|
||||
|
||||
policyID := target.PolicyID()
|
||||
if policyID > 0 {
|
||||
policy, err := f.storagePolicyClient.GetPolicyByID(ctx, policyID)
|
||||
|
|
|
@ -191,6 +191,7 @@ type (
|
|||
Shares []*ent.Share
|
||||
EntityStoragePolicies map[int]*ent.StoragePolicy
|
||||
View *types.ExplorerView
|
||||
DirectLinks []*ent.DirectLink
|
||||
}
|
||||
|
||||
FolderSummary struct {
|
||||
|
|
|
@ -98,8 +98,9 @@ func (m *manager) GetDirectLink(ctx context.Context, urls ...*fs.URI) ([]DirectL
|
|||
}
|
||||
|
||||
if useRedirect {
|
||||
reuseExisting := !m.user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionUniqueRedirectDirectLink))
|
||||
// Use redirect source
|
||||
link, err := fileClient.CreateDirectLink(ctx, file.ID(), file.Name(), m.user.Edges.Group.SpeedLimit)
|
||||
link, err := fileClient.CreateDirectLink(ctx, file.ID(), file.Name(), m.user.Edges.Group.SpeedLimit, reuseExisting)
|
||||
if err != nil {
|
||||
ae.Add(url.String(), err)
|
||||
continue
|
||||
|
|
|
@ -116,6 +116,16 @@ func GetSource(c *gin.Context) {
|
|||
c.JSON(200, serializer.Response{Data: res})
|
||||
}
|
||||
|
||||
func DeleteDirectLink(c *gin.Context) {
|
||||
err := explorer.DeleteDirectLink(c)
|
||||
if err != nil {
|
||||
c.JSON(200, serializer.Err(c, err))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, serializer.Response{})
|
||||
}
|
||||
|
||||
// Thumb 获取文件缩略图
|
||||
func Thumb(c *gin.Context) {
|
||||
service := ParametersFromContext[*explorer.FileThumbService](c, explorer.FileThumbParameterCtx{})
|
||||
|
|
|
@ -697,10 +697,18 @@ func initMasterRouter(dep dependency.Dep) *gin.Engine {
|
|||
)
|
||||
|
||||
// 取得文件外链
|
||||
file.PUT("source",
|
||||
controllers.FromJSON[explorer.GetDirectLinkService](explorer.GetDirectLinkParamCtx{}),
|
||||
middleware.ValidateBatchFileCount(dep, explorer.GetDirectLinkParamCtx{}),
|
||||
controllers.GetSource)
|
||||
source := file.Group("source")
|
||||
{
|
||||
source.PUT("",
|
||||
controllers.FromJSON[explorer.GetDirectLinkService](explorer.GetDirectLinkParamCtx{}),
|
||||
middleware.ValidateBatchFileCount(dep, explorer.GetDirectLinkParamCtx{}),
|
||||
controllers.GetSource,
|
||||
)
|
||||
source.DELETE(":id",
|
||||
middleware.HashID(hashid.SourceLinkID),
|
||||
controllers.DeleteDirectLink,
|
||||
)
|
||||
}
|
||||
// Patch view
|
||||
file.PATCH("view",
|
||||
controllers.FromJSON[explorer.PatchViewService](explorer.PatchViewParameterCtx{}),
|
||||
|
|
|
@ -120,6 +120,31 @@ func (s *GetDirectLinkService) Get(c *gin.Context) ([]DirectLinkResponse, error)
|
|||
return BuildDirectLinkResponse(res), err
|
||||
}
|
||||
|
||||
func DeleteDirectLink(c *gin.Context) error {
|
||||
dep := dependency.FromContext(c)
|
||||
user := inventory.UserFromContext(c)
|
||||
m := manager.NewFileManager(dep, user)
|
||||
defer m.Recycle()
|
||||
|
||||
linkId := hashid.FromContext(c)
|
||||
linkClient := dep.DirectLinkClient()
|
||||
ctx := context.WithValue(c, inventory.LoadDirectLinkFile{}, true)
|
||||
link, err := linkClient.GetByID(ctx, linkId)
|
||||
if err != nil || link.Edges.File == nil {
|
||||
return serializer.NewError(serializer.CodeNotFound, "Direct link not found", err)
|
||||
}
|
||||
|
||||
if link.Edges.File.OwnerID != user.ID {
|
||||
return serializer.NewError(serializer.CodeNotFound, "Direct link not found", err)
|
||||
}
|
||||
|
||||
if err := linkClient.Delete(c, link.ID); err != nil {
|
||||
return serializer.NewError(serializer.CodeDBError, "Failed to delete direct link", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type (
|
||||
// ListFileParameterCtx define key fore ListFileService
|
||||
ListFileParameterCtx struct{}
|
||||
|
|
|
@ -239,6 +239,14 @@ type ExtendedInfo struct {
|
|||
Shares []Share `json:"shares,omitempty"`
|
||||
Entities []Entity `json:"entities,omitempty"`
|
||||
View *types.ExplorerView `json:"view,omitempty"`
|
||||
DirectLinks []DirectLink `json:"direct_links,omitempty"`
|
||||
}
|
||||
|
||||
type DirectLink struct {
|
||||
ID string `json:"id"`
|
||||
URL string `json:"url"`
|
||||
Downloaded int `json:"downloaded"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type StoragePolicy struct {
|
||||
|
@ -372,16 +380,20 @@ func BuildExtendedInfo(ctx context.Context, u *ent.User, f fs.File, hasher hashi
|
|||
return nil
|
||||
}
|
||||
|
||||
dep := dependency.FromContext(ctx)
|
||||
base := dep.SettingProvider().SiteURL(ctx)
|
||||
|
||||
ext := &ExtendedInfo{
|
||||
StoragePolicy: BuildStoragePolicy(extendedInfo.StoragePolicy, hasher),
|
||||
StorageUsed: extendedInfo.StorageUsed,
|
||||
Entities: lo.Map(f.Entities(), func(e fs.Entity, index int) Entity {
|
||||
return BuildEntity(extendedInfo, e, hasher)
|
||||
}),
|
||||
DirectLinks: lo.Map(extendedInfo.DirectLinks, func(d *ent.DirectLink, index int) DirectLink {
|
||||
return BuildDirectLink(d, hasher, base)
|
||||
}),
|
||||
}
|
||||
|
||||
dep := dependency.FromContext(ctx)
|
||||
base := dep.SettingProvider().SiteURL(ctx)
|
||||
if u.ID == f.OwnerID() {
|
||||
// Only owner can see the shares settings.
|
||||
ext.Shares = lo.Map(extendedInfo.Shares, func(s *ent.Share, index int) Share {
|
||||
|
@ -393,6 +405,15 @@ func BuildExtendedInfo(ctx context.Context, u *ent.User, f fs.File, hasher hashi
|
|||
return ext
|
||||
}
|
||||
|
||||
func BuildDirectLink(d *ent.DirectLink, hasher hashid.Encoder, base *url.URL) DirectLink {
|
||||
return DirectLink{
|
||||
ID: hashid.EncodeSourceLinkID(hasher, d.ID),
|
||||
URL: routes.MasterDirectLink(base, hashid.EncodeSourceLinkID(hasher, d.ID), d.Name).String(),
|
||||
Downloaded: d.Downloads,
|
||||
CreatedAt: d.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func BuildEntity(extendedInfo *fs.FileExtendedInfo, e fs.Entity, hasher hashid.Encoder) Entity {
|
||||
var u *user.User
|
||||
createdBy := e.CreatedBy()
|
||||
|
|
Loading…
Reference in New Issue