Init V4 community edition (#2265)

* Init V4 community edition

* Init V4 community edition
This commit is contained in:
AaronLiu
2025-04-20 17:31:25 +08:00
committed by GitHub
parent da4e44b77a
commit 21d158db07
597 changed files with 119415 additions and 41692 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,97 +0,0 @@
package controllers
import (
"context"
"github.com/cloudreve/Cloudreve/v3/pkg/aria2/common"
"github.com/cloudreve/Cloudreve/v3/service/aria2"
"github.com/cloudreve/Cloudreve/v3/service/explorer"
"github.com/gin-gonic/gin"
)
// AddAria2URL 添加离线下载URL
func AddAria2URL(c *gin.Context) {
var addService aria2.BatchAddURLService
if err := c.ShouldBindJSON(&addService); err == nil {
res := addService.Add(c, common.URLTask)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// SelectAria2File 选择多文件离线下载中要下载的文件
func SelectAria2File(c *gin.Context) {
var selectService aria2.SelectFileService
if err := c.ShouldBindJSON(&selectService); err == nil {
res := selectService.Select(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// AddAria2Torrent 添加离线下载种子
func AddAria2Torrent(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.FileIDService
if err := c.ShouldBindUri(&service); err == nil {
// 获取种子内容的下载地址
res := service.CreateDownloadSession(ctx, c)
if res.Code != 0 {
c.JSON(200, res)
return
}
// 创建下载任务
var addService aria2.AddURLService
addService.URL = res.Data.(string)
if err := c.ShouldBindJSON(&addService); err == nil {
addService.URL = res.Data.(string)
res := addService.Add(c, nil, common.URLTask)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
} else {
c.JSON(200, ErrorResponse(err))
}
}
// CancelAria2Download 取消或删除aria2离线下载任务
func CancelAria2Download(c *gin.Context) {
var selectService aria2.DownloadTaskService
if err := c.ShouldBindUri(&selectService); err == nil {
res := selectService.Delete(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// ListDownloading 获取正在下载中的任务
func ListDownloading(c *gin.Context) {
var service aria2.DownloadListService
if err := c.ShouldBindQuery(&service); err == nil {
res := service.Downloading(c, CurrentUser(c))
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// ListFinished 获取已完成的任务
func ListFinished(c *gin.Context) {
var service aria2.DownloadListService
if err := c.ShouldBindQuery(&service); err == nil {
res := service.Finished(c, CurrentUser(c))
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}

View File

@@ -1,140 +1,126 @@
package controllers
import (
model "github.com/cloudreve/Cloudreve/v3/models"
"path"
"strconv"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/cloudreve/Cloudreve/v3/service/callback"
"fmt"
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/driver/upyun"
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs"
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/manager"
"github.com/cloudreve/Cloudreve/v4/pkg/logging"
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
"github.com/cloudreve/Cloudreve/v4/pkg/util"
"github.com/cloudreve/Cloudreve/v4/service/callback"
"github.com/gin-gonic/gin"
"github.com/qiniu/go-sdk/v7/auth/qbox"
)
// RemoteCallback 远程上传回调
func RemoteCallback(c *gin.Context) {
var callbackBody callback.RemoteUploadCallbackService
if err := c.ShouldBindJSON(&callbackBody); err == nil {
res := callback.ProcessCallback(callbackBody, c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// QiniuCallback 七牛上传回调
func QiniuCallback(c *gin.Context) {
var callbackBody callback.UploadCallbackService
if err := c.ShouldBindJSON(&callbackBody); err == nil {
res := callback.ProcessCallback(callbackBody, c)
if res.Code != 0 {
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: res.Msg})
} else {
c.JSON(200, res)
}
} else {
c.JSON(401, ErrorResponse(err))
}
}
// OSSCallback 阿里云OSS上传回调
func OSSCallback(c *gin.Context) {
var callbackBody callback.UploadCallbackService
if err := c.ShouldBindJSON(&callbackBody); err == nil {
if callbackBody.PicInfo == "," {
callbackBody.PicInfo = ""
}
res := callback.ProcessCallback(callbackBody, c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// UpyunCallback 又拍云上传回调
func UpyunCallback(c *gin.Context) {
var callbackBody callback.UpyunCallbackService
if err := c.ShouldBind(&callbackBody); err == nil {
if callbackBody.Code != 200 {
util.Log().Debug(
"Upload callback returned error code:%d, message: %s",
callbackBody.Code,
callbackBody.Message,
)
// RemoteCallback process callback request to complete upload
func ProcessCallback(failedStatusCode int, generalResp bool) gin.HandlerFunc {
return func(c *gin.Context) {
err := callback.ProcessCallback(c)
if err != nil {
if generalResp {
c.JSON(failedStatusCode, serializer.GeneralUploadCallbackFailed{Error: err.Error()})
} else {
c.JSON(failedStatusCode, serializer.Err(c, err))
}
return
}
res := callback.ProcessCallback(callbackBody, c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
c.JSON(200, serializer.Response{})
}
}
// OneDriveCallback OneDrive上传完成客户端回调
func OneDriveCallback(c *gin.Context) {
var callbackBody callback.OneDriveCallback
if err := c.ShouldBindJSON(&callbackBody); err == nil {
res := callbackBody.PreProcess(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
// QiniuCallbackAuth 七牛回调签名验证
func QiniuCallbackValidate(c *gin.Context) {
session := c.MustGet(manager.UploadSessionCtx).(*fs.UploadSession)
// 验证回调是否来自qiniu
mac := qbox.NewMac(session.Policy.AccessKey, session.Policy.SecretKey)
ok, err := mac.VerifyCallback(c.Request)
if err != nil {
util.Log().Debug("Failed to verify callback request: %s", err)
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: "Failed to verify callback request."})
c.Abort()
return
}
if !ok {
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: "Invalid signature."})
c.Abort()
return
}
c.Next()
}
// OSSCallbackValidate 阿里云OSS上传回调
func OSSCallbackValidate(c *gin.Context) {
var callbackBody callback.UploadCallbackService
if err := c.ShouldBindJSON(&callbackBody); err == nil {
uploadSession := c.MustGet(manager.UploadSessionCtx).(*fs.UploadSession)
if uploadSession.Props.Size != callbackBody.Size {
l := logging.FromContext(c)
l.Error("Callback validate failed: size mismatch, expected: %d, actual:%d", uploadSession.Props.Size, callbackBody.Size)
c.JSON(401,
serializer.GeneralUploadCallbackFailed{
Error: fmt.Sprintf("size mismatch"),
})
c.Abort()
return
}
c.Next()
} else {
c.JSON(401, ErrorResponse(err))
c.Abort()
}
}
// UpyunCallbackAuth 又拍云回调签名验证
func UpyunCallbackAuth(c *gin.Context) {
uploadSession := c.MustGet(manager.UploadSessionCtx).(*fs.UploadSession)
l := logging.FromContext(c)
if err := upyun.ValidateCallback(c, uploadSession); err != nil {
l.Error("Failed to verify callback request: %s", err)
c.JSON(401, serializer.GeneralUploadCallbackFailed{Error: "Failed to verify callback request."})
}
c.Next()
}
// OneDriveOAuth OneDrive 授权回调
func OneDriveOAuth(c *gin.Context) {
var callbackBody callback.OauthService
if err := c.ShouldBindQuery(&callbackBody); err == nil {
res := callbackBody.OdAuth(c)
redirect := model.GetSiteURL()
redirect.Path = path.Join(redirect.Path, "/admin/policy")
queries := redirect.Query()
queries.Add("code", strconv.Itoa(res.Code))
queries.Add("msg", res.Msg)
queries.Add("err", res.Error)
redirect.RawQuery = queries.Encode()
c.Redirect(303, redirect.String())
} else {
c.JSON(200, ErrorResponse(err))
}
//var callbackBody callback.OauthService
//if err := c.ShouldBindQuery(&callbackBody); err == nil {
// res := callbackBody.OdAuth(c)
// redirect := model.GetSiteURL()
// redirect.Path = path.Join(redirect.Path, "/admin/policy")
// queries := redirect.Query()
// queries.Add("code", strconv.Itoa(res.Code))
// queries.Add("msg", res.Msg)
// queries.Add("err", res.Error)
// redirect.RawQuery = queries.Encode()
// c.Redirect(303, redirect.String())
//} else {
// c.JSON(200, ErrorResponse(err))
//}
}
// GoogleDriveOAuth Google Drive 授权回调
func GoogleDriveOAuth(c *gin.Context) {
var callbackBody callback.OauthService
if err := c.ShouldBindQuery(&callbackBody); err == nil {
res := callbackBody.GDriveAuth(c)
redirect := model.GetSiteURL()
redirect.Path = path.Join(redirect.Path, "/admin/policy")
queries := redirect.Query()
queries.Add("code", strconv.Itoa(res.Code))
queries.Add("msg", res.Msg)
queries.Add("err", res.Error)
redirect.RawQuery = queries.Encode()
c.Redirect(303, redirect.String())
} else {
c.JSON(200, ErrorResponse(err))
}
}
// COSCallback COS上传完成客户端回调
func COSCallback(c *gin.Context) {
var callbackBody callback.COSCallback
if err := c.ShouldBindQuery(&callbackBody); err == nil {
res := callbackBody.PreProcess(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// S3Callback S3上传完成客户端回调
func S3Callback(c *gin.Context) {
var callbackBody callback.S3Callback
if err := c.ShouldBindQuery(&callbackBody); err == nil {
res := callbackBody.PreProcess(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
//var callbackBody callback.OauthService
//if err := c.ShouldBindQuery(&callbackBody); err == nil {
// res := callbackBody.GDriveAuth(c)
// redirect := model.GetSiteURL()
// redirect.Path = path.Join(redirect.Path, "/admin/policy")
// queries := redirect.Query()
// queries.Add("code", strconv.Itoa(res.Code))
// queries.Add("msg", res.Msg)
// queries.Add("err", res.Error)
// redirect.RawQuery = queries.Encode()
// c.Redirect(303, redirect.String())
//} else {
// c.JSON(200, ErrorResponse(err))
//}
}

View File

@@ -1,28 +1,27 @@
package controllers
import (
"github.com/cloudreve/Cloudreve/v3/service/explorer"
"errors"
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
"github.com/cloudreve/Cloudreve/v4/service/explorer"
"github.com/gin-gonic/gin"
)
// CreateDirectory 创建目录
func CreateDirectory(c *gin.Context) {
var service explorer.DirectoryService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.CreateDirectory(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// ListDirectory 列出目录下内容
func ListDirectory(c *gin.Context) {
var service explorer.DirectoryService
if err := c.ShouldBindUri(&service); err == nil {
res := service.ListDirectory(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
service := ParametersFromContext[*explorer.ListFileService](c, explorer.ListFileParameterCtx{})
resp, err := service.List(c)
if err != nil {
if errors.Is(err, explorer.ErrSSETakeOver) {
return
}
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{
Data: resp,
})
}

View File

@@ -1,421 +1,373 @@
package controllers
import (
"context"
"fmt"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
"net/http"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/service/explorer"
"github.com/cloudreve/Cloudreve/v4/pkg/request"
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
"github.com/cloudreve/Cloudreve/v4/service/explorer"
"github.com/gin-gonic/gin"
)
func DownloadArchive(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.ArchiveService
if err := c.ShouldBindUri(&service); err == nil {
service.DownloadArchived(ctx, c)
} else {
c.JSON(200, ErrorResponse(err))
service := ParametersFromContext[*explorer.ArchiveService](c, explorer.ArchiveParamCtx{})
err := service.DownloadArchived(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
}
func Archive(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// CreateArchive 创建文件压缩任务
func CreateArchive(c *gin.Context) {
service := ParametersFromContext[*explorer.ArchiveWorkflowService](c, explorer.CreateArchiveParamCtx{})
resp, err := service.CreateCompressTask(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
var service explorer.ItemIDService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Archive(ctx, c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
if resp != nil {
c.JSON(200, serializer.Response{
Data: resp,
})
}
}
// Compress 创建文件压缩任务
func Compress(c *gin.Context) {
var service explorer.ItemCompressService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.CreateCompressTask(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
// CreateRemoteDownload creates remote download task
func CreateRemoteDownload(c *gin.Context) {
service := ParametersFromContext[*explorer.DownloadWorkflowService](c, explorer.CreateDownloadParamCtx{})
resp, err := service.CreateDownloadTask(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
if resp != nil {
c.JSON(200, serializer.Response{
Data: resp,
})
}
}
// Decompress 创建文件解压缩任务
func Decompress(c *gin.Context) {
var service explorer.ItemDecompressService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.CreateDecompressTask(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
// ExtractArchive creates extract archive task
func ExtractArchive(c *gin.Context) {
service := ParametersFromContext[*explorer.ArchiveWorkflowService](c, explorer.CreateArchiveParamCtx{})
resp, err := service.CreateExtractTask(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
}
// AnonymousGetContent 匿名获取文件资源
func AnonymousGetContent(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.FileAnonymousGetService
if err := c.ShouldBindUri(&service); err == nil {
res := service.Download(ctx, c)
if res.Code != 0 {
c.JSON(200, res)
}
} else {
c.JSON(200, ErrorResponse(err))
}
}
// AnonymousPermLink Deprecated 文件签名后的永久链接
func AnonymousPermLinkDeprecated(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.FileAnonymousGetService
if err := c.ShouldBindUri(&service); err == nil {
res := service.Source(ctx, c)
// 是否需要重定向
if res.Code == -302 {
c.Redirect(302, res.Data.(string))
return
}
// 是否有错误发生
if res.Code != 0 {
c.JSON(200, res)
}
} else {
c.JSON(200, ErrorResponse(err))
if resp != nil {
c.JSON(200, serializer.Response{
Data: resp,
})
}
}
// AnonymousPermLink 文件中转后的永久直链接
func AnonymousPermLink(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
sourceLinkRaw, ok := c.Get("source_link")
if !ok {
c.JSON(200, serializer.Err(serializer.CodeFileNotFound, "", nil))
name := c.Param("name")
if err := explorer.RedirectDirectLink(c, name); err != nil {
c.JSON(404, serializer.Err(c, err))
c.Abort()
return
}
sourceLink := sourceLinkRaw.(*model.SourceLink)
service := &explorer.FileAnonymousGetService{
ID: sourceLink.FileID,
Name: sourceLink.File.Name,
}
res := service.Source(ctx, c)
// 是否需要重定向
if res.Code == -302 {
c.Redirect(302, res.Data.(string))
return
}
// 是否有错误发生
if res.Code != 0 {
c.JSON(200, res)
}
}
// GetSource 获取文件的外链地址
func GetSource(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.ItemIDService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Sources(ctx, c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
service := ParametersFromContext[*explorer.GetDirectLinkService](c, explorer.GetDirectLinkParamCtx{})
res, err := service.Get(c)
if err != nil && len(res) == 0 {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
if err != nil {
// Not fully completed
errResp := serializer.Err(c, err)
errResp.Data = res
c.JSON(200, errResp)
return
}
c.JSON(200, serializer.Response{Data: res})
}
// Thumb 获取文件缩略图
func Thumb(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
fs, err := filesystem.NewFileSystemFromContext(c)
service := ParametersFromContext[*explorer.FileThumbService](c, explorer.FileThumbParameterCtx{})
res, err := service.Get(c)
if err != nil {
c.JSON(200, serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err))
return
}
defer fs.Recycle()
// 获取文件ID
fileID, ok := c.Get("object_id")
if !ok {
c.JSON(200, serializer.Err(serializer.CodeFileNotFound, "", err))
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
// 获取缩略图
resp, err := fs.GetThumb(ctx, fileID.(uint))
c.JSON(200, serializer.Response{Data: res})
}
// FileURL get temporary file url for preview or download
func FileURL(c *gin.Context) {
service := ParametersFromContext[*explorer.FileURLService](c, explorer.FileURLParameterCtx{})
resp, err := service.Get(c)
if err != nil {
c.JSON(200, serializer.Err(serializer.CodeNotSet, "Failed to get thumbnail", err))
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
if resp.Redirect {
c.Header("Cache-Control", fmt.Sprintf("max-age=%d", resp.MaxAge))
c.Redirect(http.StatusMovedPermanently, resp.URL)
if resp != nil {
c.JSON(200, serializer.Response{
Data: resp,
})
}
}
// ServeEntity download entity content
func ServeEntity(c *gin.Context) {
service := ParametersFromContext[*explorer.EntityDownloadService](c, explorer.EntityDownloadParameterCtx{})
err := service.Serve(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
}
// CreateViewerSession creates a viewer session
func CreateViewerSession(c *gin.Context) {
service := ParametersFromContext[*explorer.CreateViewerSessionService](c, explorer.CreateViewerSessionParamCtx{})
resp, err := service.Create(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
defer resp.Content.Close()
http.ServeContent(c.Writer, c.Request, "thumb."+model.GetSettingByNameWithDefault("thumb_encode_method", "jpg"), fs.FileTarget[0].UpdatedAt, resp.Content)
}
// Preview 预览文件
func Preview(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.FileIDService
if err := c.ShouldBindUri(&service); err == nil {
res := service.PreviewContent(ctx, c, false)
// 是否需要重定向
if res.Code == -301 {
c.Redirect(302, res.Data.(string))
return
}
// 是否有错误发生
if res.Code != 0 {
c.JSON(200, res)
}
} else {
c.JSON(200, ErrorResponse(err))
}
}
// PreviewText 预览文本文件
func PreviewText(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.FileIDService
if err := c.ShouldBindUri(&service); err == nil {
res := service.PreviewContent(ctx, c, true)
// 是否有错误发生
if res.Code != 0 {
c.JSON(200, res)
}
} else {
c.JSON(200, ErrorResponse(err))
}
}
// GetDocPreview 获取DOC文件预览地址
func GetDocPreview(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.FileIDService
if err := c.ShouldBindUri(&service); err == nil {
res := service.CreateDocPreviewSession(ctx, c, true)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// CreateDownloadSession 创建文件下载会话
func CreateDownloadSession(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.FileIDService
if err := c.ShouldBindUri(&service); err == nil {
res := service.CreateDownloadSession(ctx, c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// Download 文件下载
func Download(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.DownloadService
if err := c.ShouldBindUri(&service); err == nil {
res := service.Download(ctx, c)
if res.Code != 0 {
c.JSON(200, res)
}
} else {
c.JSON(200, ErrorResponse(err))
if resp != nil {
c.JSON(200, serializer.Response{
Data: resp,
})
}
}
// PutContent 更新文件内容
func PutContent(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.FileIDService
if err := c.ShouldBindUri(&service); err == nil {
res := service.PutContent(ctx, c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
service := ParametersFromContext[*explorer.FileUpdateService](c, explorer.FileUpdateParameterCtx{})
res, err := service.PutContent(c, nil)
if err != nil {
c.JSON(200, serializer.Err(c, err))
request.BlackHole(c.Request.Body)
c.Abort()
return
}
c.JSON(200, serializer.Response{Data: res})
}
// FileUpload 本地策略文件上传
func FileUpload(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.UploadService
if err := c.ShouldBindUri(&service); err == nil {
res := service.LocalUpload(ctx, c)
c.JSON(200, res)
service := ParametersFromContext[*explorer.UploadService](c, explorer.UploadParameterCtx{})
err := service.LocalUpload(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
request.BlackHole(c.Request.Body)
} else {
c.JSON(200, ErrorResponse(err))
c.Abort()
return
}
//fileData := fsctx.FileStream{
// MIMEType: c.Request.Header.Get("Content-Type"),
// File: c.Request.Body,
// Size: fileSize,
// Name: fileName,
// VirtualPath: filePath,
// Mode: fsctx.Create,
//}
//
//// 创建文件系统
//fs, err := filesystem.NewFileSystemFromContext(c)
//if err != nil {
// c.JSON(200, serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err))
// return
//}
//
//// 非可用策略时拒绝上传
//if !fs.Policy.IsTransitUpload(fileSize) {
// request.BlackHole(c.Request.Body)
// c.JSON(200, serializer.Err(serializer.CodePolicyNotAllowed, "当前存储策略无法使用", nil))
// return
//}
//
//// 给文件系统分配钩子
//fs.Use("BeforeUpload", filesystem.HookValidateFile)
//fs.Use("BeforeUpload", filesystem.HookValidateCapacity)
//fs.Use("AfterUploadCanceled", filesystem.HookDeleteTempFile)
//fs.Use("AfterUploadCanceled", filesystem.HookGiveBackCapacity)
//fs.Use("AfterUpload", filesystem.GenericAfterUpload)
//fs.Use("AfterValidateFailed", filesystem.HookDeleteTempFile)
//fs.Use("AfterValidateFailed", filesystem.HookGiveBackCapacity)
//fs.Use("AfterUploadFailed", filesystem.HookGiveBackCapacity)
//
//// 执行上传
//ctx = context.WithValue(ctx, fsctx.ValidateCapacityOnceCtx, &sync.Once{})
//uploadCtx := context.WithValue(ctx, fsctx.GinCtx, c)
//err = fs.Upload(uploadCtx, &fileData)
//if err != nil {
// c.JSON(200, serializer.Err(serializer.CodeUploadFailed, err.Error(), err))
// return
//}
//
//c.JSON(200, serializer.Response{
// Code: 0,
//})
c.JSON(200, serializer.Response{})
}
// DeleteUploadSession 删除上传会话
func DeleteUploadSession(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.UploadSessionService
if err := c.ShouldBindUri(&service); err == nil {
res := service.Delete(ctx, c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// DeleteAllUploadSession 删除全部上传会话
func DeleteAllUploadSession(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
res := explorer.DeleteAllUploadSession(ctx, c)
c.JSON(200, res)
}
// GetUploadSession 创建上传会话
func GetUploadSession(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.CreateUploadSessionService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Create(ctx, c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// SearchFile 搜索文件
func SearchFile(c *gin.Context) {
var service explorer.ItemSearchService
if err := c.ShouldBindUri(&service); err != nil {
c.JSON(200, ErrorResponse(err))
service := ParametersFromContext[*explorer.DeleteUploadSessionService](c, explorer.DeleteUploadSessionParameterCtx{})
err := service.Delete(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
if err := c.ShouldBindQuery(&service); err != nil {
c.JSON(200, ErrorResponse(err))
c.JSON(200, serializer.Response{})
}
// CreateUploadSession 创建上传会话
func CreateUploadSession(c *gin.Context) {
service := ParametersFromContext[*explorer.CreateUploadSessionService](c, explorer.CreateUploadSessionParameterCtx{})
resp, err := service.Create(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
res := service.Search(c)
c.JSON(200, res)
c.JSON(200, serializer.Response{
Data: resp,
})
}
// CreateFile 创建空白文件
func CreateFile(c *gin.Context) {
var service explorer.SingleFileService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Create(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
service := ParametersFromContext[*explorer.CreateFileService](c, explorer.CreateFileParameterCtx{})
resp, err := service.Create(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{
Data: resp,
})
}
// RenameFile Renames a file.
func RenameFile(c *gin.Context) {
service := ParametersFromContext[*explorer.RenameFileService](c, explorer.RenameFileParameterCtx{})
resp, err := service.Rename(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{
Data: resp,
})
}
// MoveFile Moves or Copy files.
func MoveFile(c *gin.Context) {
service := ParametersFromContext[*explorer.MoveFileService](c, explorer.MoveFileParameterCtx{})
if err := service.Move(c); err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{})
}
// Delete 删除文件或目录
func Delete(c *gin.Context) {
service := ParametersFromContext[*explorer.DeleteFileService](c, explorer.DeleteFileParameterCtx{})
err := service.Delete(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{})
}
// Restore restore file or directory
func Restore(c *gin.Context) {
service := ParametersFromContext[*explorer.DeleteFileService](c, explorer.DeleteFileParameterCtx{})
err := service.Restore(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{})
}
// Unlock unlocks files by given tokens
func Unlock(c *gin.Context) {
service := ParametersFromContext[*explorer.UnlockFileService](c, explorer.UnlockFileParameterCtx{})
err := service.Unlock(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{})
}
// Pin pins files by given uri
func Pin(c *gin.Context) {
service := ParametersFromContext[*explorer.PinFileService](c, explorer.PinFileParameterCtx{})
err := service.PinFile(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
return
}
c.JSON(200, serializer.Response{})
}
// Unpin unpins files by given uri
func Unpin(c *gin.Context) {
service := ParametersFromContext[*explorer.PinFileService](c, explorer.PinFileParameterCtx{})
err := service.UnpinFile(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
return
}
c.JSON(200, serializer.Response{})
}
// PatchMetadata patch metadata
func PatchMetadata(c *gin.Context) {
service := ParametersFromContext[*explorer.PatchMetadataService](c, explorer.PatchMetadataParameterCtx{})
err := service.Patch(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{})
}
// GetFileInfo gets file info
func GetFileInfo(c *gin.Context) {
service := ParametersFromContext[*explorer.GetFileInfoService](c, explorer.GetFileInfoParameterCtx{})
resp, err := service.Get(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{
Data: resp,
})
}
// SetCurrentVersion sets current version
func SetCurrentVersion(c *gin.Context) {
service := ParametersFromContext[*explorer.SetCurrentVersionService](c, explorer.SetCurrentVersionParamCtx{})
err := service.Set(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{})
}
// DeleteVersion deletes a version
func DeleteVersion(c *gin.Context) {
service := ParametersFromContext[*explorer.DeleteVersionService](c, explorer.DeleteVersionParamCtx{})
err := service.Delete(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{})
}

View File

@@ -1,10 +1,9 @@
package controllers
import (
"context"
"encoding/json"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
)
@@ -44,7 +43,7 @@ func ErrorResponse(err error) serializer.Response {
// 处理 Validator 产生的错误
if ve, ok := err.(validator.ValidationErrors); ok {
for _, e := range ve {
return serializer.ParamErr(
return serializer.ParamErrDeprecated(
ParamErrorMsg(e.Field(), e.Tag()),
err,
)
@@ -52,18 +51,55 @@ func ErrorResponse(err error) serializer.Response {
}
if _, ok := err.(*json.UnmarshalTypeError); ok {
return serializer.ParamErr("JSON marshall error", err)
return serializer.ParamErrDeprecated("JSON marshall error", err)
}
return serializer.ParamErr("Parameter error", err)
return serializer.ParamErrDeprecated("Parameter error", err)
}
// CurrentUser 获取当前用户
func CurrentUser(c *gin.Context) *model.User {
if user, _ := c.Get("user"); user != nil {
if u, ok := user.(*model.User); ok {
return u
// FromJSON Parse and validate JSON from request body
func FromJSON[T any](ctxKey any) gin.HandlerFunc {
return func(c *gin.Context) {
var service T
if err := c.ShouldBindJSON(&service); err == nil {
c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), ctxKey, &service))
c.Next()
} else {
c.JSON(200, ErrorResponse(err))
c.Abort()
}
}
return nil
}
// FromQuery Parse and validate form from request query
func FromQuery[T any](ctxKey any) gin.HandlerFunc {
return func(c *gin.Context) {
var service T
if err := c.ShouldBindQuery(&service); err == nil {
c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), ctxKey, &service))
c.Next()
} else {
c.JSON(200, ErrorResponse(err))
c.Abort()
}
}
}
// FromUri Parse and validate form from request uri
func FromUri[T any](ctxKey any) gin.HandlerFunc {
return func(c *gin.Context) {
var service T
if err := c.ShouldBindUri(&service); err == nil {
c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), ctxKey, &service))
c.Next()
} else {
c.JSON(200, ErrorResponse(err))
c.Abort()
}
}
}
// ParametersFromContext retrieves request parameters from context
func ParametersFromContext[T any](c *gin.Context, ctxKey any) T {
return c.Request.Context().Value(ctxKey).(T)
}

View File

@@ -1,84 +0,0 @@
package controllers
import (
"context"
"github.com/cloudreve/Cloudreve/v3/service/explorer"
"github.com/gin-gonic/gin"
)
// Delete 删除文件或目录
func Delete(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.ItemIDService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Delete(ctx, c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// Move 移动文件或目录
func Move(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.ItemMoveService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Move(ctx, c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// Copy 复制文件或目录
func Copy(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.ItemMoveService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Copy(ctx, c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// Rename 重命名文件或目录
func Rename(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.ItemRenameService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Rename(ctx, c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// Rename 重命名文件或目录
func GetProperty(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.ItemPropertyService
service.ID = c.Param("id")
if err := c.ShouldBindQuery(&service); err == nil {
res := service.GetProperty(ctx, c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}

View File

@@ -1,237 +1,78 @@
package controllers
import (
"context"
"path"
"strings"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/cloudreve/Cloudreve/v3/service/share"
"github.com/cloudreve/Cloudreve/v4/pkg/hashid"
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
"github.com/cloudreve/Cloudreve/v4/service/share"
"github.com/gin-gonic/gin"
"net/http"
)
// CreateShare 创建分享
func CreateShare(c *gin.Context) {
var service share.ShareCreateService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Create(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
service := ParametersFromContext[*share.ShareCreateService](c, share.ShareCreateParamCtx{})
uri, err := service.Upsert(c, 0)
if err != nil {
c.JSON(200, serializer.Err(c, err))
return
}
c.JSON(200, serializer.Response{Data: uri})
}
// EditShare 编辑分享
func EditShare(c *gin.Context) {
service := ParametersFromContext[*share.ShareCreateService](c, share.ShareCreateParamCtx{})
uri, err := service.Upsert(c, hashid.FromContext(c))
if err != nil {
c.JSON(200, serializer.Err(c, err))
return
}
c.JSON(200, serializer.Response{Data: uri})
}
// GetShare 查看分享
func GetShare(c *gin.Context) {
var service share.ShareGetService
if err := c.ShouldBindQuery(&service); err == nil {
res := service.Get(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
service := ParametersFromContext[*share.ShareInfoService](c, share.ShareInfoParamCtx{})
info, err := service.Get(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
return
}
c.JSON(200, serializer.Response{Data: info})
}
// ListShare 列出分享
func ListShare(c *gin.Context) {
var service share.ShareListService
if err := c.ShouldBindQuery(&service); err == nil {
res := service.List(c, CurrentUser(c))
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
service := ParametersFromContext[*share.ListShareService](c, share.ListShareParamCtx{})
resp, err := service.List(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
}
// SearchShare 搜索分享
func SearchShare(c *gin.Context) {
var service share.ShareListService
if err := c.ShouldBindQuery(&service); err == nil {
res := service.Search(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// UpdateShare 更新分享属性
func UpdateShare(c *gin.Context) {
var service share.ShareUpdateService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Update(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
if resp != nil {
c.JSON(200, serializer.Response{
Data: resp,
})
}
}
// DeleteShare 删除分享
func DeleteShare(c *gin.Context) {
var service share.Service
if err := c.ShouldBindUri(&service); err == nil {
res := service.Delete(c, CurrentUser(c))
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// GetShareDownload 创建分享下载会话
func GetShareDownload(c *gin.Context) {
var service share.Service
if err := c.ShouldBindQuery(&service); err == nil {
res := service.CreateDownloadSession(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// PreviewShare 预览分享文件内容
func PreviewShare(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service share.Service
if err := c.ShouldBindQuery(&service); err == nil {
res := service.PreviewContent(ctx, c, false)
// 是否需要重定向
if res.Code == -301 {
c.Redirect(302, res.Data.(string))
return
}
// 是否有错误发生
if res.Code != 0 {
c.JSON(200, res)
}
} else {
c.JSON(200, ErrorResponse(err))
}
}
// PreviewShareText 预览文本文件
func PreviewShareText(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service share.Service
if err := c.ShouldBindQuery(&service); err == nil {
res := service.PreviewContent(ctx, c, true)
// 是否有错误发生
if res.Code != 0 {
c.JSON(200, res)
}
} else {
c.JSON(200, ErrorResponse(err))
}
}
// PreviewShareReadme 预览文本自述文件
func PreviewShareReadme(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service share.Service
if err := c.ShouldBindQuery(&service); err == nil {
// 自述文件名限制
allowFileName := []string{"readme.txt", "readme.md"}
fileName := strings.ToLower(path.Base(service.Path))
if !util.ContainsString(allowFileName, fileName) {
c.JSON(200, serializer.ParamErr("Not a README file", nil))
}
// 必须是目录分享
if shareCtx, ok := c.Get("share"); ok {
if !shareCtx.(*model.Share).IsDir {
c.JSON(200, serializer.ParamErr("This share has no README file", nil))
}
}
res := service.PreviewContent(ctx, c, true)
// 是否有错误发生
if res.Code != 0 {
c.JSON(200, res)
}
} else {
c.JSON(200, ErrorResponse(err))
}
}
// GetShareDocPreview 创建分享Office文档预览地址
func GetShareDocPreview(c *gin.Context) {
var service share.Service
if err := c.ShouldBindQuery(&service); err == nil {
res := service.CreateDocPreviewSession(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// ListSharedFolder 列出分享的目录下的对象
func ListSharedFolder(c *gin.Context) {
var service share.Service
if err := c.ShouldBindUri(&service); err == nil {
res := service.List(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// SearchSharedFolder 搜索分享的目录下的对象
func SearchSharedFolder(c *gin.Context) {
var service share.SearchService
if err := c.ShouldBindUri(&service); err != nil {
c.JSON(200, ErrorResponse(err))
err := share.DeleteShare(c, hashid.FromContext(c))
if err != nil {
c.JSON(200, serializer.Err(c, err))
return
}
if err := c.ShouldBindQuery(&service); err != nil {
c.JSON(200, ErrorResponse(err))
return
}
res := service.Search(c)
c.JSON(200, res)
c.JSON(200, serializer.Response{})
}
// ArchiveShare 打包要下载的分享
func ArchiveShare(c *gin.Context) {
var service share.ArchiveService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Archive(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// ShareThumb 获取分享目录下文件的缩略图
func ShareThumb(c *gin.Context) {
var service share.Service
if err := c.ShouldBindQuery(&service); err == nil {
res := service.Thumb(c)
if res.Code >= 0 {
c.JSON(200, res)
}
} else {
c.JSON(200, ErrorResponse(err))
}
}
// GetUserShare 查看给定用户的分享
func GetUserShare(c *gin.Context) {
var service share.ShareUserGetService
if err := c.ShouldBindQuery(&service); err == nil {
res := service.Get(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
func ShareRedirect(c *gin.Context) {
service := ParametersFromContext[*share.ShortLinkRedirectService](c, share.ShortLinkRedirectParamCtx{})
c.Redirect(http.StatusFound, service.RedirectTo(c))
}

View File

@@ -1,55 +1,33 @@
package controllers
import (
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/cloudreve/Cloudreve/v3/pkg/wopi"
"github.com/cloudreve/Cloudreve/v4/application/constants"
"github.com/cloudreve/Cloudreve/v4/application/dependency"
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
"github.com/cloudreve/Cloudreve/v4/service/basic"
"github.com/gin-gonic/gin"
"github.com/mojocn/base64Captcha"
)
// SiteConfig 获取站点全局配置
func SiteConfig(c *gin.Context) {
siteConfig := model.GetSettingByNames(
"siteName",
"login_captcha",
"reg_captcha",
"email_active",
"forget_captcha",
"email_active",
"themes",
"defaultTheme",
"home_view_method",
"share_view_method",
"authn_enabled",
"captcha_ReCaptchaKey",
"captcha_type",
"captcha_TCaptcha_CaptchaAppId",
"register_enabled",
"show_app_promotion",
)
service := ParametersFromContext[*basic.GetSettingService](c, basic.GetSettingParamCtx{})
var wopiExts []string
if wopi.Default != nil {
wopiExts = wopi.Default.AvailableExts()
}
// 如果已登录,则同时返回用户信息和标签
user, _ := c.Get("user")
if user, ok := user.(*model.User); ok {
c.JSON(200, serializer.BuildSiteConfig(siteConfig, user, wopiExts))
resp, err := service.GetSiteConfig(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.BuildSiteConfig(siteConfig, nil, wopiExts))
c.JSON(200, serializer.Response{
Data: resp,
})
}
// Ping 状态检查页面
func Ping(c *gin.Context) {
version := conf.BackendVersion
if conf.IsPro == "true" {
version := constants.BackendVersion
if constants.IsProBool {
version += "-pro"
}
@@ -61,81 +39,41 @@ func Ping(c *gin.Context) {
// Captcha 获取验证码
func Captcha(c *gin.Context) {
options := model.GetSettingByNames(
"captcha_IsShowHollowLine",
"captcha_IsShowNoiseDot",
"captcha_IsShowNoiseText",
"captcha_IsShowSlimeLine",
"captcha_IsShowSineLine",
)
// 验证码配置
var configD = base64Captcha.ConfigCharacter{
Height: model.GetIntSetting("captcha_height", 60),
Width: model.GetIntSetting("captcha_width", 240),
//const CaptchaModeNumber:数字,CaptchaModeAlphabet:字母,CaptchaModeArithmetic:算术,CaptchaModeNumberAlphabet:数字字母混合.
Mode: model.GetIntSetting("captcha_mode", 3),
ComplexOfNoiseText: model.GetIntSetting("captcha_ComplexOfNoiseText", 0),
ComplexOfNoiseDot: model.GetIntSetting("captcha_ComplexOfNoiseDot", 0),
IsShowHollowLine: model.IsTrueVal(options["captcha_IsShowHollowLine"]),
IsShowNoiseDot: model.IsTrueVal(options["captcha_IsShowNoiseDot"]),
IsShowNoiseText: model.IsTrueVal(options["captcha_IsShowNoiseText"]),
IsShowSlimeLine: model.IsTrueVal(options["captcha_IsShowSlimeLine"]),
IsShowSineLine: model.IsTrueVal(options["captcha_IsShowSineLine"]),
CaptchaLen: model.GetIntSetting("captcha_CaptchaLen", 6),
}
// 生成验证码
idKeyD, capD := base64Captcha.GenerateCaptcha("", configD)
// 将验证码UID存入Session以便后续验证
util.SetSession(c, map[string]interface{}{
"captchaID": idKeyD,
})
// 将验证码图像编码为Base64
base64stringD := base64Captcha.CaptchaWriteToBase64Encoding(capD)
c.JSON(200, serializer.Response{
Code: 0,
Data: base64stringD,
Data: basic.GetCaptchaImage(c),
})
}
// Manifest 获取manifest.json
func Manifest(c *gin.Context) {
options := model.GetSettingByNames(
"siteName",
"siteTitle",
"pwa_small_icon",
"pwa_medium_icon",
"pwa_large_icon",
"pwa_display",
"pwa_theme_color",
"pwa_background_color",
)
settingClient := dependency.FromContext(c).SettingProvider()
siteOpts := settingClient.SiteBasic(c)
pwaOpts := settingClient.PWA(c)
c.Header("Cache-Control", "public, no-cache")
c.JSON(200, map[string]interface{}{
"short_name": options["siteName"],
"name": options["siteTitle"],
"short_name": siteOpts.Name,
"name": siteOpts.Name,
"icons": []map[string]string{
{
"src": options["pwa_small_icon"],
"src": pwaOpts.SmallIcon,
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon",
},
{
"src": options["pwa_medium_icon"],
"src": pwaOpts.MediumIcon,
"type": "image/png",
"sizes": "192x192",
},
{
"src": options["pwa_large_icon"],
"src": pwaOpts.LargeIcon,
"type": "image/png",
"sizes": "512x512",
},
},
"start_url": ".",
"display": options["pwa_display"],
"theme_color": options["pwa_theme_color"],
"background_color": options["pwa_background_color"],
"display": pwaOpts.Display,
"theme_color": pwaOpts.ThemeColor,
"background_color": pwaOpts.BackgroundColor,
})
}

View File

@@ -1,138 +1,118 @@
package controllers
import (
"context"
"fmt"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/service/admin"
"github.com/cloudreve/Cloudreve/v3/service/aria2"
"github.com/cloudreve/Cloudreve/v3/service/explorer"
"github.com/cloudreve/Cloudreve/v3/service/node"
"github.com/cloudreve/Cloudreve/v4/pkg/cluster"
"github.com/cloudreve/Cloudreve/v4/pkg/downloader"
"github.com/cloudreve/Cloudreve/v4/pkg/downloader/slave"
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs"
"github.com/cloudreve/Cloudreve/v4/pkg/request"
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
"github.com/cloudreve/Cloudreve/v4/service/admin"
"github.com/cloudreve/Cloudreve/v4/service/explorer"
"github.com/cloudreve/Cloudreve/v4/service/node"
"github.com/gin-gonic/gin"
)
// SlaveUpload 从机文件上传
func SlaveUpload(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.UploadService
if err := c.ShouldBindUri(&service); err == nil {
res := service.SlaveUpload(ctx, c)
c.JSON(200, res)
service := ParametersFromContext[*explorer.UploadService](c, explorer.UploadParameterCtx{})
err := service.SlaveUpload(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
request.BlackHole(c.Request.Body)
} else {
c.JSON(200, ErrorResponse(err))
c.Abort()
return
}
c.JSON(200, serializer.Response{})
}
// SlaveGetUploadSession 从机创建上传会话
func SlaveGetUploadSession(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.SlaveCreateUploadSessionService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Create(ctx, c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
service := ParametersFromContext[*explorer.SlaveCreateUploadSessionService](c, explorer.SlaveCreateUploadSessionParamCtx{})
if err := service.Create(c); err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{})
}
// SlaveDeleteUploadSession 从机删除上传会话
func SlaveDeleteUploadSession(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
service := ParametersFromContext[*explorer.SlaveDeleteUploadSessionService](c, explorer.SlaveDeleteUploadSessionParamCtx{})
if err := service.Delete(c); err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
var service explorer.UploadSessionService
if err := c.ShouldBindUri(&service); err == nil {
res := service.SlaveDelete(ctx, c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
c.JSON(200, serializer.Response{})
}
// SlaveServeEntity download entity content
func SlaveServeEntity(c *gin.Context) {
service := ParametersFromContext[*explorer.EntityDownloadService](c, explorer.EntityDownloadParameterCtx{})
err := service.SlaveServe(c)
if err != nil {
c.JSON(400, serializer.Err(c, err))
c.Abort()
return
}
}
// SlaveDownload 从机文件下载,此请求返回的HTTP状态码不全为200
func SlaveDownload(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.SlaveDownloadService
if err := c.ShouldBindUri(&service); err == nil {
res := service.ServeFile(ctx, c, true)
if res.Code != 0 {
c.JSON(400, res)
}
} else {
c.JSON(400, ErrorResponse(err))
// SlaveMeta retrieve media metadata
func SlaveMeta(c *gin.Context) {
service := ParametersFromContext[*explorer.SlaveMetaService](c, explorer.SlaveMetaParamCtx{})
res, err := service.MediaMeta(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
}
// SlavePreview 从机文件预览
func SlavePreview(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.SlaveDownloadService
if err := c.ShouldBindUri(&service); err == nil {
res := service.ServeFile(ctx, c, false)
if res.Code != 0 {
c.JSON(200, res)
}
} else {
c.JSON(200, ErrorResponse(err))
}
c.JSON(200, serializer.NewResponseWithGobData(c, res))
}
// SlaveThumb 从机文件缩略图
func SlaveThumb(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.SlaveFileService
if err := c.ShouldBindUri(&service); err == nil {
res := service.Thumb(ctx, c)
if res.Code != 0 {
c.JSON(200, res)
}
} else {
c.JSON(200, ErrorResponse(err))
service := ParametersFromContext[*explorer.SlaveThumbService](c, explorer.SlaveThumbParamCtx{})
err := service.Thumb(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
}
// SlaveDelete 从机删除
func SlaveDelete(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var service explorer.SlaveFilesService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Delete(ctx, c)
c.JSON(200, res)
service := ParametersFromContext[*explorer.SlaveDeleteFileService](c, explorer.SlaveDeleteFileParamCtx{})
if failed, err := service.Delete(c); err != nil {
c.JSON(200, serializer.NewResponseWithGobData(c, serializer.Response{
Code: serializer.CodeNotFullySuccess,
Data: failed,
Msg: fmt.Sprintf("Failed to delete %d files(s)", len(failed)),
Error: err.Error(),
}))
} else {
c.JSON(200, ErrorResponse(err))
c.JSON(200, serializer.Response{Data: ""})
}
}
// SlavePing 从机测试
func SlavePing(c *gin.Context) {
var service admin.SlavePingService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Test()
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
service := ParametersFromContext[*admin.SlavePingService](c, admin.SlavePingParameterCtx{})
if err := service.Test(c); err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{})
}
// SlaveList 从机列出文件
@@ -146,101 +126,167 @@ func SlaveList(c *gin.Context) {
}
}
// SlaveHeartbeat 接受主机心跳包
func SlaveHeartbeat(c *gin.Context) {
var service serializer.NodePingReq
if err := c.ShouldBindJSON(&service); err == nil {
res := node.HandleMasterHeartbeat(&service)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
// SlaveDownloadTaskCreate creates a download task on slave
func SlaveDownloadTaskCreate(c *gin.Context) {
service := ParametersFromContext[*slave.CreateSlaveDownload](c, node.CreateSlaveDownloadTaskParamCtx{})
d := c.MustGet(downloader.DownloaderCtxKey).(downloader.Downloader)
handle, err := d.CreateTask(c, service.Url, service.Options)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.NewResponseWithGobData(c, handle))
}
// SlaveAria2Create 创建 Aria2 任务
func SlaveAria2Create(c *gin.Context) {
var service serializer.SlaveAria2Call
if err := c.ShouldBindJSON(&service); err == nil {
res := aria2.Add(c, &service)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
// SlaveDownloadTaskStatus 查询从机 Aria2 任务状态
func SlaveDownloadTaskStatus(c *gin.Context) {
service := ParametersFromContext[*slave.GetSlaveDownload](c, node.GetSlaveDownloadTaskParamCtx{})
d := c.MustGet(downloader.DownloaderCtxKey).(downloader.Downloader)
info, err := d.Info(c, service.Handle)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.NewResponseWithGobData(c, info))
}
// SlaveAria2Status 查询从机 Aria2 任务状态
func SlaveAria2Status(c *gin.Context) {
var service serializer.SlaveAria2Call
if err := c.ShouldBindJSON(&service); err == nil {
res := aria2.SlaveStatus(c, &service)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
// SlaveCancelDownloadTask 取消从机离线下载任务
func SlaveCancelDownloadTask(c *gin.Context) {
service := ParametersFromContext[*slave.CancelSlaveDownload](c, node.CancelSlaveDownloadTaskParamCtx{})
d := c.MustGet(downloader.DownloaderCtxKey).(downloader.Downloader)
err := d.Cancel(c, service.Handle)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{})
}
// SlaveCancelAria2Task 取消从机离线下载任务
func SlaveCancelAria2Task(c *gin.Context) {
var service serializer.SlaveAria2Call
if err := c.ShouldBindJSON(&service); err == nil {
res := aria2.SlaveCancel(c, &service)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
// SlaveSelectFilesToDownload 从机选取离线下载文件
func SlaveSelectFilesToDownload(c *gin.Context) {
service := ParametersFromContext[*slave.SetSlaveFilesToDownload](c, node.SelectSlaveDownloadFilesParamCtx{})
d := c.MustGet(downloader.DownloaderCtxKey).(downloader.Downloader)
err := d.SetFilesToDownload(c, service.Handle, service.Args...)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{})
}
// SlaveSelectTask 从机选取离线下载文件
func SlaveSelectTask(c *gin.Context) {
var service serializer.SlaveAria2Call
if err := c.ShouldBindJSON(&service); err == nil {
res := aria2.SlaveSelect(c, &service)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
// SlaveTestDownloader 从机测试下载器连接
func SlaveTestDownloader(c *gin.Context) {
d := c.MustGet(downloader.DownloaderCtxKey).(downloader.Downloader)
res, err := d.Test(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
}
// SlaveCreateTransferTask 从机创建中转任务
func SlaveCreateTransferTask(c *gin.Context) {
var service serializer.SlaveTransferReq
if err := c.ShouldBindJSON(&service); err == nil {
res := explorer.CreateTransferTask(c, &service)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// SlaveNotificationPush 处理从机发送的消息推送
func SlaveNotificationPush(c *gin.Context) {
var service node.SlaveNotificationService
if err := c.ShouldBindUri(&service); err == nil {
res := service.HandleSlaveNotificationPush(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
c.JSON(200, serializer.Response{Data: res})
}
// SlaveGetOauthCredential 从机获取主机的OneDrive存储策略凭证
func SlaveGetOauthCredential(c *gin.Context) {
var service node.OauthCredentialService
if err := c.ShouldBindUri(&service); err == nil {
res := service.Get(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
func SlaveGetCredential(c *gin.Context) {
service := ParametersFromContext[*node.OauthCredentialService](c, node.OauthCredentialParamCtx{})
cred, err := service.Get(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.NewResponseWithGobData(c, cred))
}
// SlaveSelectTask 从机删除离线下载临时文件
func SlaveDeleteTempFile(c *gin.Context) {
var service serializer.SlaveAria2Call
if err := c.ShouldBindJSON(&service); err == nil {
res := aria2.SlaveDeleteTemp(c, &service)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
// SlaveCreateTask creates tasks and register it in registry
func SlaveCreateTask(c *gin.Context) {
service := ParametersFromContext[*cluster.CreateSlaveTask](c, node.CreateSlaveTaskParamCtx{})
taskId, err := node.CreateTaskInSlave(service, c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.NewResponseWithGobData(c, taskId))
}
// SlaveCreateTask creates tasks and register it in registry
func SlaveGetTask(c *gin.Context) {
service := ParametersFromContext[*node.GetSlaveTaskService](c, node.GetSlaveTaskParamCtx{})
task, err := service.Get(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.NewResponseWithGobData(c, task))
}
func SlaveCleanupFolder(c *gin.Context) {
service := ParametersFromContext[*cluster.FolderCleanup](c, node.FolderCleanupParamCtx{})
if err := node.Cleanup(service, c); err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{})
}
func StatelessPrepareUpload(c *gin.Context) {
service := ParametersFromContext[*fs.StatelessPrepareUploadService](c, node.StatelessPrepareUploadParamCtx{})
uploadSession, err := node.StatelessPrepareUpload(service, c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.NewResponseWithGobData(c, uploadSession))
}
func StatelessCompleteUpload(c *gin.Context) {
service := ParametersFromContext[*fs.StatelessCompleteUploadService](c, node.StatelessCompleteUploadParamCtx{})
_, err := node.StatelessCompleteUpload(service, c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{})
}
func StatelessOnUploadFailed(c *gin.Context) {
service := ParametersFromContext[*fs.StatelessOnUploadFailedService](c, node.StatelessOnUploadFailedParamCtx{})
if err := node.StatelessOnUploadFailed(service, c); err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{})
}
func StatelessCreateFile(c *gin.Context) {
service := ParametersFromContext[*fs.StatelessCreateFileService](c, node.StatelessCreateFileParamCtx{})
if err := node.StatelessCreateFile(service, c); err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{})
}

View File

@@ -1,39 +0,0 @@
package controllers
import (
"github.com/cloudreve/Cloudreve/v3/service/explorer"
"github.com/gin-gonic/gin"
)
// CreateFilterTag 创建文件分类标签
func CreateFilterTag(c *gin.Context) {
var service explorer.FilterTagCreateService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Create(c, CurrentUser(c))
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// CreateLinkTag 创建目录快捷方式标签
func CreateLinkTag(c *gin.Context) {
var service explorer.LinkTagCreateService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Create(c, CurrentUser(c))
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// DeleteTag 删除标签
func DeleteTag(c *gin.Context) {
var service explorer.TagService
if err := c.ShouldBindUri(&service); err == nil {
res := service.Delete(c, CurrentUser(c))
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}

View File

@@ -1,216 +1,173 @@
package controllers
import (
"encoding/json"
"fmt"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/authn"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/pkg/thumb"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/cloudreve/Cloudreve/v3/service/user"
"github.com/duo-labs/webauthn/webauthn"
"github.com/cloudreve/Cloudreve/v4/application/dependency"
"github.com/cloudreve/Cloudreve/v4/ent"
"github.com/cloudreve/Cloudreve/v4/inventory"
"github.com/cloudreve/Cloudreve/v4/pkg/hashid"
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
"github.com/cloudreve/Cloudreve/v4/pkg/util"
"github.com/cloudreve/Cloudreve/v4/service/share"
"github.com/cloudreve/Cloudreve/v4/service/user"
"github.com/gin-gonic/gin"
"github.com/samber/lo"
)
// StartLoginAuthn 开始注册WebAuthn登录
func StartLoginAuthn(c *gin.Context) {
userName := c.Param("username")
expectedUser, err := model.GetActiveUserByEmail(userName)
res, err := user.PreparePasskeyLogin(c)
if err != nil {
c.JSON(200, serializer.Err(serializer.CodeUserNotFound, "", err))
c.JSON(200, serializer.Err(c, err))
return
}
instance, err := authn.NewAuthnInstance()
if err != nil {
c.JSON(200, serializer.Err(serializer.CodeInitializeAuthn, "Cannot initialize authn", err))
return
}
options, sessionData, err := instance.BeginLogin(expectedUser)
if err != nil {
c.JSON(200, ErrorResponse(err))
return
}
val, err := json.Marshal(sessionData)
if err != nil {
c.JSON(200, ErrorResponse(err))
return
}
util.SetSession(c, map[string]interface{}{
"registration-session": val,
})
c.JSON(200, serializer.Response{Code: 0, Data: options})
c.JSON(200, serializer.Response{Data: res})
}
// FinishLoginAuthn 完成注册WebAuthn登录
func FinishLoginAuthn(c *gin.Context) {
userName := c.Param("username")
expectedUser, err := model.GetActiveUserByEmail(userName)
service := ParametersFromContext[*user.FinishPasskeyLoginService](c, user.FinishPasskeyLoginParameterCtx{})
u, err := service.FinishPasskeyLogin(c)
if err != nil {
c.JSON(200, serializer.Err(serializer.CodeUserNotFound, "", err))
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
sessionDataJSON := util.GetSession(c, "registration-session").([]byte)
var sessionData webauthn.SessionData
err = json.Unmarshal(sessionDataJSON, &sessionData)
instance, err := authn.NewAuthnInstance()
if err != nil {
c.JSON(200, serializer.Err(serializer.CodeInitializeAuthn, "Cannot initialize authn", err))
return
}
_, err = instance.FinishLogin(expectedUser, sessionData, c.Request)
if err != nil {
c.JSON(200, serializer.Err(serializer.CodeWebAuthnCredentialError, "Verification failed", err))
return
}
util.SetSession(c, map[string]interface{}{
"user_id": expectedUser.ID,
})
c.JSON(200, serializer.BuildUserResponse(expectedUser))
util.WithValue(c, inventory.UserCtx{}, u)
}
// StartRegAuthn 开始注册WebAuthn信息
func StartRegAuthn(c *gin.Context) {
currUser := CurrentUser(c)
instance, err := authn.NewAuthnInstance()
res, err := user.PreparePasskeyRegister(c)
if err != nil {
c.JSON(200, serializer.Err(serializer.CodeInitializeAuthn, "Cannot initialize authn", err))
c.JSON(200, serializer.Err(c, err))
return
}
options, sessionData, err := instance.BeginRegistration(currUser)
if err != nil {
c.JSON(200, ErrorResponse(err))
return
}
val, err := json.Marshal(sessionData)
if err != nil {
c.JSON(200, ErrorResponse(err))
return
}
util.SetSession(c, map[string]interface{}{
"registration-session": val,
})
c.JSON(200, serializer.Response{Code: 0, Data: options})
c.JSON(200, serializer.Response{Data: res})
}
// FinishRegAuthn 完成注册WebAuthn信息
func FinishRegAuthn(c *gin.Context) {
currUser := CurrentUser(c)
sessionDataJSON := util.GetSession(c, "registration-session").([]byte)
var sessionData webauthn.SessionData
err := json.Unmarshal(sessionDataJSON, &sessionData)
instance, err := authn.NewAuthnInstance()
service := ParametersFromContext[*user.FinishPasskeyRegisterService](c, user.FinishPasskeyRegisterParameterCtx{})
res, err := service.FinishPasskeyRegister(c)
if err != nil {
c.JSON(200, serializer.Err(serializer.CodeInitializeAuthn, "Cannot initialize authn", err))
c.JSON(200, serializer.Err(c, err))
return
}
credential, err := instance.FinishRegistration(currUser, sessionData, c.Request)
c.JSON(200, serializer.Response{Data: res})
}
// UserDeletePasskey deletes user passkey
func UserDeletePasskey(c *gin.Context) {
service := ParametersFromContext[*user.DeletePasskeyService](c, user.DeletePasskeyParameterCtx{})
err := service.DeletePasskey(c)
if err != nil {
c.JSON(200, ErrorResponse(err))
c.JSON(200, serializer.Err(c, err))
return
}
err = currUser.RegisterAuthn(credential)
c.JSON(200, serializer.Response{})
}
// UserLoginValidation validates user login request
func UserLoginValidation(c *gin.Context) {
service := ParametersFromContext[*user.UserLoginService](c, user.LoginParameterCtx{})
expectedUser, twoFaSession, err := service.Login(c)
if err != nil {
c.JSON(200, ErrorResponse(err))
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
if twoFaSession == "" {
// No 2FA required, proceed
util.WithValue(c, inventory.UserCtx{}, expectedUser)
c.Next()
return
}
c.JSON(200, serializer.Response{Code: serializer.CodeNotFullySuccess, Data: twoFaSession})
c.Abort()
}
// UserLogin2FAValidation validates user OTP code
func UserLogin2FAValidation(c *gin.Context) {
service := ParametersFromContext[*user.OtpValidationService](c, user.OtpValidationParameterCtx{})
expectedUser, err := service.Verify2FA(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
util.WithValue(c, inventory.UserCtx{}, expectedUser)
c.Next()
}
// UserIssueToken generates new token pair for user
func UserIssueToken(c *gin.Context) {
resp, err := user.IssueToken(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{
Code: 0,
Data: map[string]interface{}{
"id": credential.ID,
"fingerprint": fmt.Sprintf("% X", credential.Authenticator.AAGUID),
},
Data: resp,
})
}
// UserLogin 用户登录
func UserLogin(c *gin.Context) {
var service user.UserLoginService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Login(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
// UserRefreshToken refreshes token pair for user
func UserRefreshToken(c *gin.Context) {
service := ParametersFromContext[*user.RefreshTokenService](c, user.RefreshTokenParameterCtx{})
resp, err := service.Refresh(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{
Data: resp,
})
}
// UserRegister 用户注册
func UserRegister(c *gin.Context) {
var service user.UserRegisterService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Register(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// User2FALogin 用户二步验证登录
func User2FALogin(c *gin.Context) {
var service user.Enable2FA
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Login(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
service := ParametersFromContext[*user.UserRegisterService](c, user.RegisterParameterCtx{})
c.JSON(200, service.Register(c))
}
// UserSendReset 发送密码重设邮件
func UserSendReset(c *gin.Context) {
var service user.UserResetEmailService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Reset(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
service := ParametersFromContext[*user.UserResetEmailService](c, user.UserResetEmailParameterCtx{})
if err := service.Reset(c); err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{})
}
// UserReset 重设密码
func UserReset(c *gin.Context) {
var service user.UserResetService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Reset(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
service := ParametersFromContext[*user.UserResetService](c, user.UserResetParameterCtx{})
res, err := service.Reset(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{Data: res})
}
// UserActivate 用户激活
func UserActivate(c *gin.Context) {
var service user.SettingService
if err := c.ShouldBindUri(&service); err == nil {
res := service.Activate(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
c.JSON(200, user.ActivateUser(c))
}
// UserSignOut 用户退出登录
@@ -221,92 +178,63 @@ func UserSignOut(c *gin.Context) {
// UserMe 获取当前登录的用户
func UserMe(c *gin.Context) {
currUser := CurrentUser(c)
res := serializer.BuildUserResponse(*currUser)
c.JSON(200, res)
dep := dependency.FromContext(c)
c.JSON(200, serializer.Response{
Data: user.BuildUser(inventory.UserFromContext(c), dep.HashIDEncoder()),
})
}
// UserGet 获取用户信息
func UserGet(c *gin.Context) {
u, err := user.GetUser(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
isAnonymous := inventory.IsAnonymousUser(inventory.UserFromContext(c))
redactLevel := user.RedactLevelUser
if isAnonymous {
redactLevel = user.RedactLevelAnonymous
}
c.JSON(200, serializer.Response{
Data: user.BuildUserRedacted(u, redactLevel, dependency.FromContext(c).HashIDEncoder()),
})
}
// UserStorage 获取用户的存储信息
func UserStorage(c *gin.Context) {
currUser := CurrentUser(c)
res := serializer.BuildUserStorageResponse(*currUser)
c.JSON(200, res)
}
// UserTasks 获取任务队列
func UserTasks(c *gin.Context) {
var service user.SettingListService
if err := c.ShouldBindQuery(&service); err == nil {
res := service.ListTasks(c, CurrentUser(c))
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
res, err := user.GetUserCapacity(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{
Data: res,
})
}
// UserSetting 获取用户设定
func UserSetting(c *gin.Context) {
var service user.SettingService
if err := c.ShouldBindUri(&service); err == nil {
res := service.Settings(c, CurrentUser(c))
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// UseGravatar 设定头像使用全球通用
func UseGravatar(c *gin.Context) {
u := CurrentUser(c)
if err := u.Update(map[string]interface{}{"avatar": "gravatar"}); err != nil {
c.JSON(200, serializer.Err(serializer.CodeDBError, "无法更新头像", err))
res, err := user.GetUserSettings(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{})
c.JSON(200, serializer.Response{
Data: res,
})
}
// UploadAvatar 从文件上传头像
func UploadAvatar(c *gin.Context) {
// 取得头像上传大小限制
maxSize := model.GetIntSetting("avatar_size", 2097152)
if c.Request.ContentLength == -1 || c.Request.ContentLength > int64(maxSize) {
request.BlackHole(c.Request.Body)
c.JSON(200, serializer.Err(serializer.CodeFileTooLarge, "", nil))
return
}
// 取得上传的文件
file, err := c.FormFile("avatar")
if err != nil {
c.JSON(200, serializer.ParamErr("Failed to read avatar file data", err))
return
}
// 初始化头像
r, err := file.Open()
if err != nil {
c.JSON(200, serializer.ParamErr("Failed to read avatar file data", err))
return
}
avatar, err := thumb.NewThumbFromFile(r, file.Filename)
if err != nil {
c.JSON(200, serializer.ParamErr("Invalid image", err))
return
}
// 创建头像
u := CurrentUser(c)
err = avatar.CreateAvatar(u.ID)
if err != nil {
c.JSON(200, serializer.Err(serializer.CodeIOFailed, "Failed to create avatar file", err))
return
}
// 保存头像标记
if err := u.Update(map[string]interface{}{
"avatar": "file",
}); err != nil {
c.JSON(200, serializer.DBErr("Failed to update avatar attribute", err))
if err := user.UpdateUserAvatar(c); err != nil {
c.JSON(200, serializer.Err(c, err))
return
}
@@ -315,84 +243,158 @@ func UploadAvatar(c *gin.Context) {
// GetUserAvatar 获取用户头像
func GetUserAvatar(c *gin.Context) {
var service user.AvatarService
if err := c.ShouldBindUri(&service); err == nil {
res := service.Get(c)
if res.Code == -301 {
// 重定向到gravatar
c.Redirect(301, res.Data.(string))
}
} else {
c.JSON(200, ErrorResponse(err))
service := ParametersFromContext[*user.GetAvatarService](c, user.GetAvatarServiceParamsCtx{})
err := service.Get(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
}
// UpdateOption 更改用户设定
func UpdateOption(c *gin.Context) {
var service user.SettingUpdateService
if err := c.ShouldBindUri(&service); err == nil {
var (
subService user.OptionsChangeHandler
subErr error
)
switch service.Option {
case "nick":
subService = &user.ChangerNick{}
case "homepage":
subService = &user.HomePage{}
case "password":
subService = &user.PasswordChange{}
case "2fa":
subService = &user.Enable2FA{}
case "authn":
subService = &user.DeleteWebAuthn{}
case "theme":
subService = &user.ThemeChose{}
default:
subService = &user.ChangerNick{}
}
subErr = c.ShouldBindJSON(subService)
if subErr != nil {
c.JSON(200, ErrorResponse(subErr))
return
}
res := subService.Update(c, CurrentUser(c))
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
service := ParametersFromContext[*user.PatchUserSetting](c, user.PatchUserSettingParamsCtx{})
err := service.Patch(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{})
//var service user.SettingUpdateService
//if err := c.ShouldBindUri(&service); err == nil {
// var (
// subService user.OptionsChangeHandler
// subErr error
// )
//
// switch service.Option {
// case "nick":
// subService = &user.ChangerNick{}
// case "vip":
// subService = &user.VIPUnsubscribe{}
// case "qq":
// subService = &user.QQBind{}
// case "policy":
// subService = &user.PolicyChange{}
// case "homepage":
// subService = &user.HomePage{}
// case "password":
// subService = &user.PasswordChange{}
// case "2fa":
// subService = &user.Enable2FA{}
// case "authn":
// subService = &user.DeleteWebAuthn{}
// case "theme":
// subService = &user.ThemeChose{}
// default:
// subService = &user.ChangerNick{}
// }
//
// subErr = c.ShouldBindJSON(subService)
// if subErr != nil {
// c.JSON(200, ErrorResponse(subErr))
// return
// }
//
// res := subService.Update(c, CurrentUser(c))
// c.JSON(200, res)
//
//} else {
// c.JSON(200, ErrorResponse(err))
//}
}
// UserInit2FA 初始化二步验证
func UserInit2FA(c *gin.Context) {
var service user.SettingService
if err := c.ShouldBindUri(&service); err == nil {
res := service.Init2FA(c, CurrentUser(c))
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
secret, err := user.Init2FA(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
}
// UserPrepareCopySession generates URL for copy session
func UserPrepareCopySession(c *gin.Context) {
var service user.CopySessionService
res := service.Prepare(c, CurrentUser(c))
c.JSON(200, res)
c.JSON(200, serializer.Response{
Data: secret,
})
}
// UserPerformCopySession copy to create new session or refresh current session
func UserPerformCopySession(c *gin.Context) {
var service user.CopySessionService
if err := c.ShouldBindUri(&service); err == nil {
res := service.Copy(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
//var service user.CopySessionService
//if err := c.ShouldBindUri(&service); err == nil {
// res := service.Copy(c)
// c.JSON(200, res)
//} else {
// c.JSON(200, ErrorResponse(err))
//}
}
// UserPrepareLogin validates precondition for login
func UserPrepareLogin(c *gin.Context) {
service := ParametersFromContext[*user.PrepareLoginService](c, user.PrepareLoginParameterCtx{})
res, err := service.Prepare(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{Data: res})
}
// UserSearch Search user by keyword
func UserSearch(c *gin.Context) {
service := ParametersFromContext[*user.SearchUserService](c, user.SearchUserParamCtx{})
u, err := service.Search(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
hasher := dependency.FromContext(c).HashIDEncoder()
c.JSON(200, serializer.Response{
Data: lo.Map(u, func(item *ent.User, index int) user.User {
return user.BuildUserRedacted(item, user.RedactLevelUser, hasher)
}),
})
}
// GetGroupList list all groups for options
func GetGroupList(c *gin.Context) {
u, err := user.ListAllGroups(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
hasher := dependency.FromContext(c).HashIDEncoder()
c.JSON(200, serializer.Response{
Data: lo.Map(u, func(item *ent.Group, index int) *user.Group {
g := user.BuildGroup(item, hasher)
return user.RedactedGroup(g)
}),
})
}
// ListPublicShare lists all public shares for given user
func ListPublicShare(c *gin.Context) {
service := ParametersFromContext[*share.ListShareService](c, share.ListShareParamCtx{})
resp, err := service.ListInUserProfile(c, hashid.FromContext(c))
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
if resp != nil {
c.JSON(200, serializer.Response{
Data: resp,
})
}
}

View File

@@ -1,104 +1,101 @@
package controllers
import (
"context"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/cloudreve/Cloudreve/v3/pkg/webdav"
"github.com/cloudreve/Cloudreve/v3/service/setting"
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
"github.com/cloudreve/Cloudreve/v4/service/setting"
"github.com/gin-gonic/gin"
"net/http"
"sync"
)
var handler *webdav.Handler
func init() {
handler = &webdav.Handler{
Prefix: "/dav",
LockSystem: make(map[uint]webdav.LockSystem),
Mutex: &sync.Mutex{},
}
}
// ServeWebDAV 处理WebDAV相关请求
func ServeWebDAV(c *gin.Context) {
fs, err := filesystem.NewFileSystemFromContext(c)
// ListDavAccounts lists all WebDAV accounts.
func ListDavAccounts(c *gin.Context) {
service := ParametersFromContext[*setting.ListDavAccountsService](c, setting.ListDavAccountParamCtx{})
resp, err := service.List(c)
if err != nil {
util.Log().Warning("Failed to initialize filesystem for WebDAV%s", err)
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
if webdavCtx, ok := c.Get("webdav"); ok {
application := webdavCtx.(*model.Webdav)
// 重定根目录
if application.Root != "/" {
if exist, root := fs.IsPathExist(application.Root); exist {
root.Position = ""
root.Name = "/"
fs.Root = root
}
}
// 检查是否只读
if application.Readonly {
switch c.Request.Method {
case "DELETE", "PUT", "MKCOL", "COPY", "MOVE":
c.Status(http.StatusForbidden)
return
}
}
// 更新Context
c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), fsctx.WebDAVCtx, application))
}
handler.ServeHTTP(c.Writer, c.Request, fs)
}
// GetWebDAVAccounts 获取webdav账号列表
func GetWebDAVAccounts(c *gin.Context) {
var service setting.WebDAVListService
if err := c.ShouldBindUri(&service); err == nil {
res := service.Accounts(c, CurrentUser(c))
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
if resp != nil {
c.JSON(200, serializer.Response{
Data: resp,
})
}
}
// DeleteWebDAVAccounts 删除WebDAV账户
func DeleteWebDAVAccounts(c *gin.Context) {
var service setting.WebDAVAccountService
if err := c.ShouldBindUri(&service); err == nil {
res := service.Delete(c, CurrentUser(c))
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
// CreateDAVAccounts 创建WebDAV账户
func CreateDAVAccounts(c *gin.Context) {
service := ParametersFromContext[*setting.CreateDavAccountService](c, setting.CreateDavAccountParamCtx{})
resp, err := service.Create(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{
Data: resp,
})
}
// UpdateWebDAVAccounts 更改WebDAV账户只读性和是否使用代理服务
func UpdateWebDAVAccounts(c *gin.Context) {
var service setting.WebDAVAccountUpdateService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Update(c, CurrentUser(c))
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
// UpdateDAVAccounts updates WebDAV accounts.
func UpdateDAVAccounts(c *gin.Context) {
service := ParametersFromContext[*setting.CreateDavAccountService](c, setting.CreateDavAccountParamCtx{})
resp, err := service.Update(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{
Data: resp,
})
}
// CreateWebDAVAccounts 创建WebDAV账户
func CreateWebDAVAccounts(c *gin.Context) {
var service setting.WebDAVAccountCreateService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Create(c, CurrentUser(c))
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
// DeleteDAVAccounts deletes WebDAV accounts.
func DeleteDAVAccounts(c *gin.Context) {
err := setting.DeleteDavAccount(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{})
}
//
//// DeleteWebDAVAccounts 删除WebDAV账户
//func DeleteWebDAVAccounts(c *gin.Context) {
// var service setting.WebDAVAccountService
// if err := c.ShouldBindUri(&service); err == nil {
// res := service.Delete(c, CurrentUser(c))
// c.JSON(200, res)
// } else {
// c.JSON(200, ErrorResponse(err))
// }
//}
//
//// DeleteWebDAVMounts 删除WebDAV挂载
//func DeleteWebDAVMounts(c *gin.Context) {
// var service setting.WebDAVListService
// if err := c.ShouldBindUri(&service); err == nil {
// res := service.Unmount(c, CurrentUser(c))
// c.JSON(200, res)
// } else {
// c.JSON(200, ErrorResponse(err))
// }
//}
//
//
//// CreateWebDAVMounts 创建WebDAV目录挂载
//func CreateWebDAVMounts(c *gin.Context) {
// var service setting.WebDAVMountCreateService
// if err := c.ShouldBindJSON(&service); err == nil {
// res := service.Create(c, CurrentUser(c))
// c.JSON(200, res)
// } else {
// c.JSON(200, ErrorResponse(err))
// }
//}

View File

@@ -1,10 +1,8 @@
package controllers
import (
"context"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/pkg/wopi"
"github.com/cloudreve/Cloudreve/v3/service/explorer"
"github.com/cloudreve/Cloudreve/v4/pkg/wopi"
"github.com/cloudreve/Cloudreve/v4/service/explorer"
"github.com/gin-gonic/gin"
"net/http"
)
@@ -35,43 +33,46 @@ func GetFile(c *gin.Context) {
// PutFile Puts file content
func PutFile(c *gin.Context) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
service := &explorer.FileIDService{}
res := service.PutContent(ctx, c)
switch res.Code {
case serializer.CodeFileTooLarge:
c.Status(http.StatusRequestEntityTooLarge)
c.Header(wopi.ServerErrorHeader, res.Error)
case serializer.CodeNotFound:
c.Status(http.StatusNotFound)
c.Header(wopi.ServerErrorHeader, res.Error)
case 0:
c.Status(http.StatusOK)
default:
service := &explorer.WopiService{}
err := service.PutContent(c)
if err != nil {
c.Status(http.StatusInternalServerError)
c.Header(wopi.ServerErrorHeader, res.Error)
c.Header(wopi.ServerErrorHeader, err.Error())
}
}
// ModifyFile Modify file properties
func ModifyFile(c *gin.Context) {
action := c.GetHeader(wopi.OverwriteHeader)
var (
service explorer.WopiService
err error
)
switch action {
case wopi.MethodLock, wopi.MethodRefreshLock, wopi.MethodUnlock:
c.Status(http.StatusOK)
return
case wopi.MethodRename:
var service explorer.WopiService
err := service.Rename(c)
if err != nil {
c.Status(http.StatusInternalServerError)
c.Header(wopi.ServerErrorHeader, err.Error())
case wopi.MethodLock:
err = service.Lock(c)
if err == nil {
return
}
case wopi.MethodRefreshLock:
err = service.RefreshLock(c)
if err == nil {
return
}
case wopi.MethodUnlock:
err = service.Unlock(c)
if err == nil {
return
}
default:
c.Status(http.StatusNotImplemented)
return
}
if err != nil {
c.Status(http.StatusInternalServerError)
c.Header(wopi.ServerErrorHeader, err.Error())
return
}
}

View File

@@ -0,0 +1,68 @@
package controllers
import (
"github.com/cloudreve/Cloudreve/v4/pkg/hashid"
"github.com/cloudreve/Cloudreve/v4/pkg/queue"
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
"github.com/cloudreve/Cloudreve/v4/service/explorer"
"github.com/gin-gonic/gin"
)
func ListTasks(c *gin.Context) {
service := ParametersFromContext[*explorer.ListTaskService](c, explorer.ListTaskParamCtx{})
resp, err := service.ListTasks(c)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
if resp != nil {
c.JSON(200, serializer.Response{
Data: resp,
})
}
}
func GetTaskPhaseProgress(c *gin.Context) {
taskId := hashid.FromContext(c)
resp, err := explorer.TaskPhaseProgress(c, taskId)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
if resp != nil {
c.JSON(200, serializer.Response{
Data: resp,
})
} else {
c.JSON(200, serializer.Response{Data: queue.Progresses{}})
}
}
func SetDownloadTaskTarget(c *gin.Context) {
taskId := hashid.FromContext(c)
service := ParametersFromContext[*explorer.SetDownloadFilesService](c, explorer.SetDownloadFilesParamCtx{})
err := service.SetDownloadFiles(c, taskId)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{})
}
func CancelDownloadTask(c *gin.Context) {
taskId := hashid.FromContext(c)
err := explorer.CancelDownloadTask(c, taskId)
if err != nil {
c.JSON(200, serializer.Err(c, err))
c.Abort()
return
}
c.JSON(200, serializer.Response{})
}