init
This commit is contained in:
117
pkg/serializer/aria2.go
Normal file
117
pkg/serializer/aria2.go
Normal file
@ -0,0 +1,117 @@
|
||||
package serializer
|
||||
|
||||
import (
|
||||
"path"
|
||||
"time"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/aria2/rpc"
|
||||
)
|
||||
|
||||
// DownloadListResponse 下载列表响应条目
|
||||
type DownloadListResponse struct {
|
||||
UpdateTime time.Time `json:"update"`
|
||||
UpdateInterval int `json:"interval"`
|
||||
Name string `json:"name"`
|
||||
Status int `json:"status"`
|
||||
Dst string `json:"dst"`
|
||||
Total uint64 `json:"total"`
|
||||
Downloaded uint64 `json:"downloaded"`
|
||||
Speed int `json:"speed"`
|
||||
Info rpc.StatusInfo `json:"info"`
|
||||
NodeName string `json:"node"`
|
||||
}
|
||||
|
||||
// FinishedListResponse 已完成任务条目
|
||||
type FinishedListResponse struct {
|
||||
Name string `json:"name"`
|
||||
GID string `json:"gid"`
|
||||
Status int `json:"status"`
|
||||
Dst string `json:"dst"`
|
||||
Error string `json:"error"`
|
||||
Total uint64 `json:"total"`
|
||||
Files []rpc.FileInfo `json:"files"`
|
||||
TaskStatus int `json:"task_status"`
|
||||
TaskError string `json:"task_error"`
|
||||
CreateTime time.Time `json:"create"`
|
||||
UpdateTime time.Time `json:"update"`
|
||||
NodeName string `json:"node"`
|
||||
}
|
||||
|
||||
// BuildFinishedListResponse 构建已完成任务条目
|
||||
func BuildFinishedListResponse(tasks []model.Download) Response {
|
||||
resp := make([]FinishedListResponse, 0, len(tasks))
|
||||
|
||||
for i := 0; i < len(tasks); i++ {
|
||||
fileName := tasks[i].StatusInfo.BitTorrent.Info.Name
|
||||
if len(tasks[i].StatusInfo.Files) == 1 {
|
||||
fileName = path.Base(tasks[i].StatusInfo.Files[0].Path)
|
||||
}
|
||||
|
||||
// 过滤敏感信息
|
||||
for i2 := 0; i2 < len(tasks[i].StatusInfo.Files); i2++ {
|
||||
tasks[i].StatusInfo.Files[i2].Path = path.Base(tasks[i].StatusInfo.Files[i2].Path)
|
||||
}
|
||||
|
||||
download := FinishedListResponse{
|
||||
Name: fileName,
|
||||
GID: tasks[i].GID,
|
||||
Status: tasks[i].Status,
|
||||
Error: tasks[i].Error,
|
||||
Dst: tasks[i].Dst,
|
||||
Total: tasks[i].TotalSize,
|
||||
Files: tasks[i].StatusInfo.Files,
|
||||
TaskStatus: -1,
|
||||
UpdateTime: tasks[i].UpdatedAt,
|
||||
CreateTime: tasks[i].CreatedAt,
|
||||
NodeName: tasks[i].NodeName,
|
||||
}
|
||||
|
||||
if tasks[i].Task != nil {
|
||||
download.TaskError = tasks[i].Task.Error
|
||||
download.TaskStatus = tasks[i].Task.Status
|
||||
}
|
||||
|
||||
resp = append(resp, download)
|
||||
}
|
||||
|
||||
return Response{Data: resp}
|
||||
}
|
||||
|
||||
// BuildDownloadingResponse 构建正在下载的列表响应
|
||||
func BuildDownloadingResponse(tasks []model.Download, intervals map[uint]int) Response {
|
||||
resp := make([]DownloadListResponse, 0, len(tasks))
|
||||
|
||||
for i := 0; i < len(tasks); i++ {
|
||||
fileName := ""
|
||||
if len(tasks[i].StatusInfo.Files) > 0 {
|
||||
fileName = path.Base(tasks[i].StatusInfo.Files[0].Path)
|
||||
}
|
||||
|
||||
// 过滤敏感信息
|
||||
tasks[i].StatusInfo.Dir = ""
|
||||
for i2 := 0; i2 < len(tasks[i].StatusInfo.Files); i2++ {
|
||||
tasks[i].StatusInfo.Files[i2].Path = path.Base(tasks[i].StatusInfo.Files[i2].Path)
|
||||
}
|
||||
|
||||
interval := 10
|
||||
if actualInterval, ok := intervals[tasks[i].ID]; ok {
|
||||
interval = actualInterval
|
||||
}
|
||||
|
||||
resp = append(resp, DownloadListResponse{
|
||||
UpdateTime: tasks[i].UpdatedAt,
|
||||
UpdateInterval: interval,
|
||||
Name: fileName,
|
||||
Status: tasks[i].Status,
|
||||
Dst: tasks[i].Dst,
|
||||
Total: tasks[i].TotalSize,
|
||||
Downloaded: tasks[i].DownloadedSize,
|
||||
Speed: tasks[i].Speed,
|
||||
Info: tasks[i].StatusInfo,
|
||||
NodeName: tasks[i].NodeName,
|
||||
})
|
||||
}
|
||||
|
||||
return Response{Data: resp}
|
||||
}
|
21
pkg/serializer/auth.go
Normal file
21
pkg/serializer/auth.go
Normal file
@ -0,0 +1,21 @@
|
||||
package serializer
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// RequestRawSign 待签名的HTTP请求
|
||||
type RequestRawSign struct {
|
||||
Path string
|
||||
Header string
|
||||
Body string
|
||||
}
|
||||
|
||||
// NewRequestSignString 返回JSON格式的待签名字符串
|
||||
func NewRequestSignString(path, header, body string) string {
|
||||
req := RequestRawSign{
|
||||
Path: path,
|
||||
Header: header,
|
||||
Body: body,
|
||||
}
|
||||
res, _ := json.Marshal(req)
|
||||
return string(res)
|
||||
}
|
262
pkg/serializer/error.go
Normal file
262
pkg/serializer/error.go
Normal file
@ -0,0 +1,262 @@
|
||||
package serializer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AppError 应用错误,实现了error接口
|
||||
type AppError struct {
|
||||
Code int
|
||||
Msg string
|
||||
RawError error
|
||||
}
|
||||
|
||||
// NewError 返回新的错误对象
|
||||
func NewError(code int, msg string, err error) AppError {
|
||||
return AppError{
|
||||
Code: code,
|
||||
Msg: msg,
|
||||
RawError: err,
|
||||
}
|
||||
}
|
||||
|
||||
// NewErrorFromResponse 从 serializer.Response 构建错误
|
||||
func NewErrorFromResponse(resp *Response) AppError {
|
||||
return AppError{
|
||||
Code: resp.Code,
|
||||
Msg: resp.Msg,
|
||||
RawError: errors.New(resp.Error),
|
||||
}
|
||||
}
|
||||
|
||||
// WithError 将应用error携带标准库中的error
|
||||
func (err *AppError) WithError(raw error) AppError {
|
||||
err.RawError = raw
|
||||
return *err
|
||||
}
|
||||
|
||||
// Error 返回业务代码确定的可读错误信息
|
||||
func (err AppError) Error() string {
|
||||
return err.Msg
|
||||
}
|
||||
|
||||
// 三位数错误编码为复用http原本含义
|
||||
// 五位数错误编码为应用自定义错误
|
||||
// 五开头的五位数错误编码为服务器端错误,比如数据库操作失败
|
||||
// 四开头的五位数错误编码为客户端错误,有时候是客户端代码写错了,有时候是用户操作错误
|
||||
const (
|
||||
// CodeNotFullySuccess 未完全成功
|
||||
CodeNotFullySuccess = 203
|
||||
// CodeCheckLogin 未登录
|
||||
CodeCheckLogin = 401
|
||||
// CodeNoPermissionErr 未授权访问
|
||||
CodeNoPermissionErr = 403
|
||||
// CodeNotFound 资源未找到
|
||||
CodeNotFound = 404
|
||||
// CodeConflict 资源冲突
|
||||
CodeConflict = 409
|
||||
// CodeUploadFailed 上传出错
|
||||
CodeUploadFailed = 40002
|
||||
// CodeCreateFolderFailed 目录创建失败
|
||||
CodeCreateFolderFailed = 40003
|
||||
// CodeObjectExist 对象已存在
|
||||
CodeObjectExist = 40004
|
||||
// CodeSignExpired 签名过期
|
||||
CodeSignExpired = 40005
|
||||
// CodePolicyNotAllowed 当前存储策略不允许
|
||||
CodePolicyNotAllowed = 40006
|
||||
// CodeGroupNotAllowed 用户组无法进行此操作
|
||||
CodeGroupNotAllowed = 40007
|
||||
// CodeAdminRequired 非管理用户组
|
||||
CodeAdminRequired = 40008
|
||||
// CodeMasterNotFound 主机节点未注册
|
||||
CodeMasterNotFound = 40009
|
||||
// CodeUploadSessionExpired 上传会话已过期
|
||||
CodeUploadSessionExpired = 40011
|
||||
// CodeInvalidChunkIndex 无效的分片序号
|
||||
CodeInvalidChunkIndex = 40012
|
||||
// CodeInvalidContentLength 无效的正文长度
|
||||
CodeInvalidContentLength = 40013
|
||||
// CodePhoneRequired 未绑定手机
|
||||
CodePhoneRequired = 40010
|
||||
// CodeBatchSourceSize 超出批量获取外链限制
|
||||
CodeBatchSourceSize = 40014
|
||||
// CodeBatchAria2Size 超出最大 Aria2 任务数量限制
|
||||
CodeBatchAria2Size = 40015
|
||||
// CodeParentNotExist 父目录不存在
|
||||
CodeParentNotExist = 40016
|
||||
// CodeUserBaned 用户不活跃
|
||||
CodeUserBaned = 40017
|
||||
// CodeUserNotActivated 用户不活跃
|
||||
CodeUserNotActivated = 40018
|
||||
// CodeFeatureNotEnabled 此功能未开启
|
||||
CodeFeatureNotEnabled = 40019
|
||||
// CodeCredentialInvalid 凭证无效
|
||||
CodeCredentialInvalid = 40020
|
||||
// CodeUserNotFound 用户不存在
|
||||
CodeUserNotFound = 40021
|
||||
// Code2FACodeErr 二步验证代码错误
|
||||
Code2FACodeErr = 40022
|
||||
// CodeLoginSessionNotExist 登录会话不存在
|
||||
CodeLoginSessionNotExist = 40023
|
||||
// CodeInitializeAuthn 无法初始化 WebAuthn
|
||||
CodeInitializeAuthn = 40024
|
||||
// CodeWebAuthnCredentialError WebAuthn 凭证无效
|
||||
CodeWebAuthnCredentialError = 40025
|
||||
// CodeCaptchaError 验证码错误
|
||||
CodeCaptchaError = 40026
|
||||
// CodeCaptchaRefreshNeeded 验证码需要刷新
|
||||
CodeCaptchaRefreshNeeded = 40027
|
||||
// CodeFailedSendEmail 邮件发送失败
|
||||
CodeFailedSendEmail = 40028
|
||||
// CodeInvalidTempLink 临时链接无效
|
||||
CodeInvalidTempLink = 40029
|
||||
// CodeTempLinkExpired 临时链接过期
|
||||
CodeTempLinkExpired = 40030
|
||||
// CodeEmailProviderBaned 邮箱后缀被禁用
|
||||
CodeEmailProviderBaned = 40031
|
||||
// CodeEmailExisted 邮箱已被使用
|
||||
CodeEmailExisted = 40032
|
||||
// CodeEmailSent 邮箱已重新发送
|
||||
CodeEmailSent = 40033
|
||||
// CodeUserCannotActivate 用户无法激活
|
||||
CodeUserCannotActivate = 40034
|
||||
// 存储策略不存在
|
||||
CodePolicyNotExist = 40035
|
||||
// 无法删除默认存储策略
|
||||
CodeDeleteDefaultPolicy = 40036
|
||||
// 存储策略下还有文件
|
||||
CodePolicyUsedByFiles = 40037
|
||||
// 存储策略绑定了用户组
|
||||
CodePolicyUsedByGroups = 40038
|
||||
// 用户组不存在
|
||||
CodeGroupNotFound = 40039
|
||||
// 对系统用户组执行非法操作
|
||||
CodeInvalidActionOnSystemGroup = 40040
|
||||
// 用户组正在被使用
|
||||
CodeGroupUsedByUser = 40041
|
||||
// 为初始用户更改用户组
|
||||
CodeChangeGroupForDefaultUser = 40042
|
||||
// 对系统用户执行非法操作
|
||||
CodeInvalidActionOnDefaultUser = 40043
|
||||
// 文件不存在
|
||||
CodeFileNotFound = 40044
|
||||
// 列取文件失败
|
||||
CodeListFilesError = 40045
|
||||
// 对系统节点进行非法操作
|
||||
CodeInvalidActionOnSystemNode = 40046
|
||||
// 创建文件系统出错
|
||||
CodeCreateFSError = 40047
|
||||
// 创建任务出错
|
||||
CodeCreateTaskError = 40048
|
||||
// 文件尺寸太大
|
||||
CodeFileTooLarge = 40049
|
||||
// 文件类型不允许
|
||||
CodeFileTypeNotAllowed = 40050
|
||||
// 用户容量不足
|
||||
CodeInsufficientCapacity = 40051
|
||||
// 对象名非法
|
||||
CodeIllegalObjectName = 40052
|
||||
// 不支持对根目录执行此操作
|
||||
CodeRootProtected = 40053
|
||||
// 当前目录下已经有同名文件正在上传中
|
||||
CodeConflictUploadOngoing = 40054
|
||||
// 文件信息不一致
|
||||
CodeMetaMismatch = 40055
|
||||
// 不支持该格式的压缩文件
|
||||
CodeUnsupportedArchiveType = 40056
|
||||
// 可用存储策略发生变化
|
||||
CodePolicyChanged = 40057
|
||||
// 分享链接无效
|
||||
CodeShareLinkNotFound = 40058
|
||||
// 不能转存自己的分享
|
||||
CodeSaveOwnShare = 40059
|
||||
// 从机无法向主机发送回调请求
|
||||
CodeSlavePingMaster = 40060
|
||||
// Cloudreve 版本不一致
|
||||
CodeVersionMismatch = 40061
|
||||
// 积分不足
|
||||
CodeInsufficientCredit = 40062
|
||||
// 用户组冲突
|
||||
CodeGroupConflict = 40063
|
||||
// 当前已处于此用户组中
|
||||
CodeGroupInvalid = 40064
|
||||
// 兑换码无效
|
||||
CodeInvalidGiftCode = 40065
|
||||
// 已绑定了QQ账号
|
||||
CodeQQBindConflict = 40066
|
||||
// QQ账号已被绑定其他账号
|
||||
CodeQQBindOtherAccount = 40067
|
||||
// QQ 未绑定对应账号
|
||||
CodeQQNotLinked = 40068
|
||||
// 密码不正确
|
||||
CodeIncorrectPassword = 40069
|
||||
// 分享无法预览
|
||||
CodeDisabledSharePreview = 40070
|
||||
// 签名无效
|
||||
CodeInvalidSign = 40071
|
||||
// 管理员无法购买用户组
|
||||
CodeFulfillAdminGroup = 40072
|
||||
// CodeDBError 数据库操作失败
|
||||
CodeDBError = 50001
|
||||
// CodeEncryptError 加密失败
|
||||
CodeEncryptError = 50002
|
||||
// CodeIOFailed IO操作失败
|
||||
CodeIOFailed = 50004
|
||||
// CodeInternalSetting 内部设置参数错误
|
||||
CodeInternalSetting = 50005
|
||||
// CodeCacheOperation 缓存操作失败
|
||||
CodeCacheOperation = 50006
|
||||
// CodeCallbackError 回调失败
|
||||
CodeCallbackError = 50007
|
||||
// 后台设置更新失败
|
||||
CodeUpdateSetting = 50008
|
||||
// 跨域策略添加失败
|
||||
CodeAddCORS = 50009
|
||||
// 节点不可用
|
||||
CodeNodeOffline = 50010
|
||||
// 文件元信息查询失败
|
||||
CodeQueryMetaFailed = 50011
|
||||
//CodeParamErr 各种奇奇怪怪的参数错误
|
||||
CodeParamErr = 40001
|
||||
// CodeNotSet 未定错误,后续尝试从error中获取
|
||||
CodeNotSet = -1
|
||||
)
|
||||
|
||||
// DBErr 数据库操作失败
|
||||
func DBErr(msg string, err error) Response {
|
||||
if msg == "" {
|
||||
msg = "Database operation failed."
|
||||
}
|
||||
return Err(CodeDBError, msg, err)
|
||||
}
|
||||
|
||||
// ParamErr 各种参数错误
|
||||
func ParamErr(msg string, err error) Response {
|
||||
if msg == "" {
|
||||
msg = "Invalid parameters."
|
||||
}
|
||||
return Err(CodeParamErr, msg, err)
|
||||
}
|
||||
|
||||
// Err 通用错误处理
|
||||
func Err(errCode int, msg string, err error) Response {
|
||||
// 底层错误是AppError,则尝试从AppError中获取详细信息
|
||||
var appError AppError
|
||||
if errors.As(err, &appError) {
|
||||
errCode = appError.Code
|
||||
err = appError.RawError
|
||||
msg = appError.Msg
|
||||
}
|
||||
|
||||
res := Response{
|
||||
Code: errCode,
|
||||
Msg: msg,
|
||||
}
|
||||
// 生产环境隐藏底层报错
|
||||
if err != nil && gin.Mode() != gin.ReleaseMode {
|
||||
res.Error = err.Error()
|
||||
}
|
||||
return res
|
||||
}
|
132
pkg/serializer/explorer.go
Normal file
132
pkg/serializer/explorer.go
Normal file
@ -0,0 +1,132 @@
|
||||
package serializer
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/hashid"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gob.Register(ObjectProps{})
|
||||
}
|
||||
|
||||
// ObjectProps 文件、目录对象的详细属性信息
|
||||
type ObjectProps struct {
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Policy string `json:"policy"`
|
||||
Size uint64 `json:"size"`
|
||||
ChildFolderNum int `json:"child_folder_num"`
|
||||
ChildFileNum int `json:"child_file_num"`
|
||||
Path string `json:"path"`
|
||||
|
||||
QueryDate time.Time `json:"query_date"`
|
||||
}
|
||||
|
||||
// ObjectList 文件、目录列表
|
||||
type ObjectList struct {
|
||||
Parent string `json:"parent,omitempty"`
|
||||
Objects []Object `json:"objects"`
|
||||
Policy *PolicySummary `json:"policy,omitempty"`
|
||||
}
|
||||
|
||||
// Object 文件或者目录
|
||||
type Object struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Thumb bool `json:"thumb"`
|
||||
Size uint64 `json:"size"`
|
||||
Type string `json:"type"`
|
||||
Date time.Time `json:"date"`
|
||||
CreateDate time.Time `json:"create_date"`
|
||||
Key string `json:"key,omitempty"`
|
||||
SourceEnabled bool `json:"source_enabled"`
|
||||
}
|
||||
|
||||
// PolicySummary 用于前端组件使用的存储策略概况
|
||||
type PolicySummary struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
MaxSize uint64 `json:"max_size"`
|
||||
FileType []string `json:"file_type"`
|
||||
}
|
||||
|
||||
// BuildObjectList 构建列目录响应
|
||||
func BuildObjectList(parent uint, objects []Object, policy *model.Policy) ObjectList {
|
||||
res := ObjectList{
|
||||
Objects: objects,
|
||||
}
|
||||
|
||||
if parent > 0 {
|
||||
res.Parent = hashid.HashID(parent, hashid.FolderID)
|
||||
}
|
||||
|
||||
if policy != nil {
|
||||
res.Policy = &PolicySummary{
|
||||
ID: hashid.HashID(policy.ID, hashid.PolicyID),
|
||||
Name: policy.Name,
|
||||
Type: policy.Type,
|
||||
MaxSize: policy.MaxSize,
|
||||
FileType: policy.OptionsSerialized.FileType,
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// Sources 获取外链的结果响应
|
||||
type Sources struct {
|
||||
URL string `json:"url"`
|
||||
Name string `json:"name"`
|
||||
Parent uint `json:"parent"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// DocPreviewSession 文档预览会话响应
|
||||
type DocPreviewSession struct {
|
||||
URL string `json:"url"`
|
||||
AccessToken string `json:"access_token,omitempty"`
|
||||
AccessTokenTTL int64 `json:"access_token_ttl,omitempty"`
|
||||
}
|
||||
|
||||
// WopiFileInfo Response for `CheckFileInfo`
|
||||
type WopiFileInfo struct {
|
||||
// Required
|
||||
BaseFileName string
|
||||
Version string
|
||||
Size int64
|
||||
|
||||
// Breadcrumb
|
||||
BreadcrumbBrandName string
|
||||
BreadcrumbBrandUrl string
|
||||
BreadcrumbFolderName string
|
||||
BreadcrumbFolderUrl string
|
||||
|
||||
// Post Message
|
||||
FileSharingPostMessage bool
|
||||
ClosePostMessage bool
|
||||
PostMessageOrigin string
|
||||
|
||||
// Other miscellaneous properties
|
||||
FileNameMaxLength int
|
||||
LastModifiedTime string
|
||||
|
||||
// User metadata
|
||||
IsAnonymousUser bool
|
||||
UserFriendlyName string
|
||||
UserId string
|
||||
OwnerId string
|
||||
|
||||
// Permission
|
||||
ReadOnly bool
|
||||
UserCanRename bool
|
||||
UserCanReview bool
|
||||
UserCanWrite bool
|
||||
|
||||
SupportsRename bool
|
||||
SupportsReviewing bool
|
||||
SupportsUpdate bool
|
||||
}
|
35
pkg/serializer/response.go
Normal file
35
pkg/serializer/response.go
Normal file
@ -0,0 +1,35 @@
|
||||
package serializer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/gob"
|
||||
)
|
||||
|
||||
// Response 基础序列化器
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
Msg string `json:"msg"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// NewResponseWithGobData 返回Data字段使用gob编码的Response
|
||||
func NewResponseWithGobData(data interface{}) Response {
|
||||
var w bytes.Buffer
|
||||
encoder := gob.NewEncoder(&w)
|
||||
if err := encoder.Encode(data); err != nil {
|
||||
return Err(CodeInternalSetting, "Failed to encode response content", err)
|
||||
}
|
||||
|
||||
return Response{Data: w.Bytes()}
|
||||
}
|
||||
|
||||
// GobDecode 将 Response 正文解码至目标指针
|
||||
func (r *Response) GobDecode(target interface{}) {
|
||||
src := r.Data.(string)
|
||||
raw := make([]byte, len(src)*len(src)/base64.StdEncoding.DecodedLen(len(src)))
|
||||
base64.StdEncoding.Decode(raw, []byte(src))
|
||||
decoder := gob.NewDecoder(bytes.NewBuffer(raw))
|
||||
decoder.Decode(target)
|
||||
}
|
113
pkg/serializer/setting.go
Normal file
113
pkg/serializer/setting.go
Normal file
@ -0,0 +1,113 @@
|
||||
package serializer
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
)
|
||||
|
||||
// SiteConfig 站点全局设置序列
|
||||
type SiteConfig struct {
|
||||
SiteName string `json:"title"`
|
||||
LoginCaptcha bool `json:"loginCaptcha"`
|
||||
RegCaptcha bool `json:"regCaptcha"`
|
||||
ForgetCaptcha bool `json:"forgetCaptcha"`
|
||||
EmailActive bool `json:"emailActive"`
|
||||
QQLogin bool `json:"QQLogin"`
|
||||
Themes string `json:"themes"`
|
||||
DefaultTheme string `json:"defaultTheme"`
|
||||
ScoreEnabled bool `json:"score_enabled"`
|
||||
ShareScoreRate string `json:"share_score_rate"`
|
||||
HomepageViewMethod string `json:"home_view_method"`
|
||||
ShareViewMethod string `json:"share_view_method"`
|
||||
Authn bool `json:"authn"`
|
||||
User User `json:"user"`
|
||||
ReCaptchaKey string `json:"captcha_ReCaptchaKey"`
|
||||
SiteNotice string `json:"site_notice"`
|
||||
CaptchaType string `json:"captcha_type"`
|
||||
TCaptchaCaptchaAppId string `json:"tcaptcha_captcha_app_id"`
|
||||
RegisterEnabled bool `json:"registerEnabled"`
|
||||
ReportEnabled bool `json:"report_enabled"`
|
||||
AppPromotion bool `json:"app_promotion"`
|
||||
WopiExts []string `json:"wopi_exts"`
|
||||
AppFeedbackLink string `json:"app_feedback"`
|
||||
AppForumLink string `json:"app_forum"`
|
||||
}
|
||||
|
||||
type task struct {
|
||||
Status int `json:"status"`
|
||||
Type int `json:"type"`
|
||||
CreateDate time.Time `json:"create_date"`
|
||||
Progress int `json:"progress"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// BuildTaskList 构建任务列表响应
|
||||
func BuildTaskList(tasks []model.Task, total int) Response {
|
||||
res := make([]task, 0, len(tasks))
|
||||
for _, t := range tasks {
|
||||
res = append(res, task{
|
||||
Status: t.Status,
|
||||
Type: t.Type,
|
||||
CreateDate: t.CreatedAt,
|
||||
Progress: t.Progress,
|
||||
Error: t.Error,
|
||||
})
|
||||
}
|
||||
|
||||
return Response{Data: map[string]interface{}{
|
||||
"total": total,
|
||||
"tasks": res,
|
||||
}}
|
||||
}
|
||||
|
||||
func checkSettingValue(setting map[string]string, key string) string {
|
||||
if v, ok := setting[key]; ok {
|
||||
return v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// BuildSiteConfig 站点全局设置
|
||||
func BuildSiteConfig(settings map[string]string, user *model.User, wopiExts []string) Response {
|
||||
var userRes User
|
||||
if user != nil {
|
||||
userRes = BuildUser(*user)
|
||||
} else {
|
||||
userRes = BuildUser(*model.NewAnonymousUser())
|
||||
}
|
||||
res := Response{
|
||||
Data: SiteConfig{
|
||||
SiteName: checkSettingValue(settings, "siteName"),
|
||||
LoginCaptcha: model.IsTrueVal(checkSettingValue(settings, "login_captcha")),
|
||||
RegCaptcha: model.IsTrueVal(checkSettingValue(settings, "reg_captcha")),
|
||||
ForgetCaptcha: model.IsTrueVal(checkSettingValue(settings, "forget_captcha")),
|
||||
EmailActive: model.IsTrueVal(checkSettingValue(settings, "email_active")),
|
||||
QQLogin: model.IsTrueVal(checkSettingValue(settings, "qq_login")),
|
||||
Themes: checkSettingValue(settings, "themes"),
|
||||
DefaultTheme: checkSettingValue(settings, "defaultTheme"),
|
||||
ScoreEnabled: model.IsTrueVal(checkSettingValue(settings, "score_enabled")),
|
||||
ShareScoreRate: checkSettingValue(settings, "share_score_rate"),
|
||||
HomepageViewMethod: checkSettingValue(settings, "home_view_method"),
|
||||
ShareViewMethod: checkSettingValue(settings, "share_view_method"),
|
||||
Authn: model.IsTrueVal(checkSettingValue(settings, "authn_enabled")),
|
||||
User: userRes,
|
||||
SiteNotice: checkSettingValue(settings, "siteNotice"),
|
||||
ReCaptchaKey: checkSettingValue(settings, "captcha_ReCaptchaKey"),
|
||||
CaptchaType: checkSettingValue(settings, "captcha_type"),
|
||||
TCaptchaCaptchaAppId: checkSettingValue(settings, "captcha_TCaptcha_CaptchaAppId"),
|
||||
RegisterEnabled: model.IsTrueVal(checkSettingValue(settings, "register_enabled")),
|
||||
ReportEnabled: model.IsTrueVal(checkSettingValue(settings, "report_enabled")),
|
||||
AppPromotion: model.IsTrueVal(checkSettingValue(settings, "show_app_promotion")),
|
||||
AppFeedbackLink: checkSettingValue(settings, "app_feedback_link"),
|
||||
AppForumLink: checkSettingValue(settings, "app_forum_link"),
|
||||
WopiExts: wopiExts,
|
||||
}}
|
||||
return res
|
||||
}
|
||||
|
||||
// VolResponse VOL query response
|
||||
type VolResponse struct {
|
||||
Signature string `json:"signature"`
|
||||
Content string `json:"content"`
|
||||
}
|
139
pkg/serializer/share.go
Normal file
139
pkg/serializer/share.go
Normal file
@ -0,0 +1,139 @@
|
||||
package serializer
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/hashid"
|
||||
)
|
||||
|
||||
// Share 分享信息序列化
|
||||
type Share struct {
|
||||
Key string `json:"key"`
|
||||
Locked bool `json:"locked"`
|
||||
IsDir bool `json:"is_dir"`
|
||||
Score int `json:"score"`
|
||||
CreateDate time.Time `json:"create_date,omitempty"`
|
||||
Downloads int `json:"downloads"`
|
||||
Views int `json:"views"`
|
||||
Expire int64 `json:"expire"`
|
||||
Preview bool `json:"preview"`
|
||||
Creator *shareCreator `json:"creator,omitempty"`
|
||||
Source *shareSource `json:"source,omitempty"`
|
||||
}
|
||||
|
||||
type shareCreator struct {
|
||||
Key string `json:"key"`
|
||||
Nick string `json:"nick"`
|
||||
GroupName string `json:"group_name"`
|
||||
}
|
||||
|
||||
type shareSource struct {
|
||||
Name string `json:"name"`
|
||||
Size uint64 `json:"size"`
|
||||
}
|
||||
|
||||
// myShareItem 我的分享列表条目
|
||||
type myShareItem struct {
|
||||
Key string `json:"key"`
|
||||
IsDir bool `json:"is_dir"`
|
||||
Score int `json:"score"`
|
||||
Password string `json:"password"`
|
||||
CreateDate time.Time `json:"create_date,omitempty"`
|
||||
Downloads int `json:"downloads"`
|
||||
RemainDownloads int `json:"remain_downloads"`
|
||||
Views int `json:"views"`
|
||||
Expire int64 `json:"expire"`
|
||||
Preview bool `json:"preview"`
|
||||
Source *shareSource `json:"source,omitempty"`
|
||||
}
|
||||
|
||||
// BuildShareList 构建我的分享列表响应
|
||||
func BuildShareList(shares []model.Share, total int) Response {
|
||||
res := make([]myShareItem, 0, total)
|
||||
now := time.Now().Unix()
|
||||
for i := 0; i < len(shares); i++ {
|
||||
item := myShareItem{
|
||||
Key: hashid.HashID(shares[i].ID, hashid.ShareID),
|
||||
IsDir: shares[i].IsDir,
|
||||
Score: shares[i].Score,
|
||||
Password: shares[i].Password,
|
||||
CreateDate: shares[i].CreatedAt,
|
||||
Downloads: shares[i].Downloads,
|
||||
Views: shares[i].Views,
|
||||
Preview: shares[i].PreviewEnabled,
|
||||
Expire: -1,
|
||||
RemainDownloads: shares[i].RemainDownloads,
|
||||
}
|
||||
if shares[i].Expires != nil {
|
||||
item.Expire = shares[i].Expires.Unix() - now
|
||||
if item.Expire == 0 {
|
||||
item.Expire = 0
|
||||
}
|
||||
}
|
||||
if shares[i].File.ID != 0 {
|
||||
item.Source = &shareSource{
|
||||
Name: shares[i].File.Name,
|
||||
Size: shares[i].File.Size,
|
||||
}
|
||||
} else if shares[i].Folder.ID != 0 {
|
||||
item.Source = &shareSource{
|
||||
Name: shares[i].Folder.Name,
|
||||
}
|
||||
}
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
return Response{Data: map[string]interface{}{
|
||||
"total": total,
|
||||
"items": res,
|
||||
}}
|
||||
}
|
||||
|
||||
// BuildShareResponse 构建获取分享信息响应
|
||||
func BuildShareResponse(share *model.Share, unlocked bool) Share {
|
||||
creator := share.Creator()
|
||||
resp := Share{
|
||||
Key: hashid.HashID(share.ID, hashid.ShareID),
|
||||
Locked: !unlocked,
|
||||
Creator: &shareCreator{
|
||||
Key: hashid.HashID(creator.ID, hashid.UserID),
|
||||
Nick: creator.Nick,
|
||||
GroupName: creator.Group.Name,
|
||||
},
|
||||
Score: share.Score,
|
||||
CreateDate: share.CreatedAt,
|
||||
}
|
||||
|
||||
// 未解锁时只返回基本信息
|
||||
if !unlocked {
|
||||
return resp
|
||||
}
|
||||
|
||||
resp.IsDir = share.IsDir
|
||||
resp.Downloads = share.Downloads
|
||||
resp.Views = share.Views
|
||||
resp.Preview = share.PreviewEnabled
|
||||
|
||||
if share.Expires != nil {
|
||||
resp.Expire = share.Expires.Unix() - time.Now().Unix()
|
||||
}
|
||||
|
||||
if share.IsDir {
|
||||
source := share.SourceFolder()
|
||||
resp.Source = &shareSource{
|
||||
Name: source.Name,
|
||||
Size: 0,
|
||||
}
|
||||
} else {
|
||||
source := share.SourceFile()
|
||||
resp.Source = &shareSource{
|
||||
Name: source.Name,
|
||||
Size: source.Size,
|
||||
}
|
||||
}
|
||||
|
||||
return resp
|
||||
|
||||
}
|
68
pkg/serializer/slave.go
Normal file
68
pkg/serializer/slave.go
Normal file
@ -0,0 +1,68 @@
|
||||
package serializer
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
)
|
||||
|
||||
// RemoteDeleteRequest 远程策略删除接口请求正文
|
||||
type RemoteDeleteRequest struct {
|
||||
Files []string `json:"files"`
|
||||
}
|
||||
|
||||
// ListRequest 远程策略列文件请求正文
|
||||
type ListRequest struct {
|
||||
Path string `json:"path"`
|
||||
Recursive bool `json:"recursive"`
|
||||
}
|
||||
|
||||
// NodePingReq 从机节点Ping请求
|
||||
type NodePingReq struct {
|
||||
SiteURL string `json:"site_url"`
|
||||
SiteID string `json:"site_id"`
|
||||
IsUpdate bool `json:"is_update"`
|
||||
CredentialTTL int `json:"credential_ttl"`
|
||||
Node *model.Node `json:"node"`
|
||||
}
|
||||
|
||||
// NodePingResp 从机节点Ping响应
|
||||
type NodePingResp struct {
|
||||
}
|
||||
|
||||
// SlaveAria2Call 从机有关Aria2的请求正文
|
||||
type SlaveAria2Call struct {
|
||||
Task *model.Download `json:"task"`
|
||||
GroupOptions map[string]interface{} `json:"group_options"`
|
||||
Files []int `json:"files"`
|
||||
}
|
||||
|
||||
// SlaveTransferReq 从机中转任务创建请求
|
||||
type SlaveTransferReq struct {
|
||||
Src string `json:"src"`
|
||||
Dst string `json:"dst"`
|
||||
Policy *model.Policy `json:"policy"`
|
||||
}
|
||||
|
||||
// Hash 返回创建请求的唯一标识,保持创建请求幂等
|
||||
func (s *SlaveTransferReq) Hash(id string) string {
|
||||
h := sha1.New()
|
||||
h.Write([]byte(fmt.Sprintf("transfer-%s-%s-%s-%d", id, s.Src, s.Dst, s.Policy.ID)))
|
||||
bs := h.Sum(nil)
|
||||
return fmt.Sprintf("%x", bs)
|
||||
}
|
||||
|
||||
const (
|
||||
SlaveTransferSuccess = "success"
|
||||
SlaveTransferFailed = "failed"
|
||||
)
|
||||
|
||||
type SlaveTransferResult struct {
|
||||
Error string
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register(SlaveTransferResult{})
|
||||
}
|
64
pkg/serializer/upload.go
Normal file
64
pkg/serializer/upload.go
Normal file
@ -0,0 +1,64 @@
|
||||
package serializer
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"time"
|
||||
)
|
||||
|
||||
// UploadPolicy slave模式下传递的上传策略
|
||||
type UploadPolicy struct {
|
||||
SavePath string `json:"save_path"`
|
||||
FileName string `json:"file_name"`
|
||||
AutoRename bool `json:"auto_rename"`
|
||||
MaxSize uint64 `json:"max_size"`
|
||||
AllowedExtension []string `json:"allowed_extension"`
|
||||
CallbackURL string `json:"callback_url"`
|
||||
}
|
||||
|
||||
// UploadCredential 返回给客户端的上传凭证
|
||||
type UploadCredential struct {
|
||||
SessionID string `json:"sessionID"`
|
||||
ChunkSize uint64 `json:"chunkSize"` // 分块大小,0 为部分快
|
||||
Expires int64 `json:"expires"` // 上传凭证过期时间, Unix 时间戳
|
||||
UploadURLs []string `json:"uploadURLs,omitempty"`
|
||||
Credential string `json:"credential,omitempty"`
|
||||
UploadID string `json:"uploadID,omitempty"`
|
||||
Callback string `json:"callback,omitempty"` // 回调地址
|
||||
Path string `json:"path,omitempty"` // 存储路径
|
||||
AccessKey string `json:"ak,omitempty"`
|
||||
KeyTime string `json:"keyTime,omitempty"` // COS用有效期
|
||||
Policy string `json:"policy,omitempty"`
|
||||
CompleteURL string `json:"completeURL,omitempty"`
|
||||
}
|
||||
|
||||
// UploadSession 上传会话
|
||||
type UploadSession struct {
|
||||
Key string // 上传会话 GUID
|
||||
UID uint // 发起者
|
||||
VirtualPath string // 用户文件路径,不含文件名
|
||||
Name string // 文件名
|
||||
Size uint64 // 文件大小
|
||||
SavePath string // 物理存储路径,包含物理文件名
|
||||
LastModified *time.Time // 可选的文件最后修改日期
|
||||
Policy model.Policy
|
||||
Callback string // 回调 URL 地址
|
||||
CallbackSecret string // 回调 URL
|
||||
UploadURL string
|
||||
UploadID string
|
||||
Credential string
|
||||
}
|
||||
|
||||
// UploadCallback 上传回调正文
|
||||
type UploadCallback struct {
|
||||
PicInfo string `json:"pic_info"`
|
||||
}
|
||||
|
||||
// GeneralUploadCallbackFailed 存储策略上传回调失败响应
|
||||
type GeneralUploadCallbackFailed struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register(UploadSession{})
|
||||
}
|
172
pkg/serializer/user.go
Normal file
172
pkg/serializer/user.go
Normal file
@ -0,0 +1,172 @@
|
||||
package serializer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/hashid"
|
||||
"github.com/duo-labs/webauthn/webauthn"
|
||||
)
|
||||
|
||||
// CheckLogin 检查登录
|
||||
func CheckLogin() Response {
|
||||
return Response{
|
||||
Code: CodeCheckLogin,
|
||||
Msg: "Login required",
|
||||
}
|
||||
}
|
||||
|
||||
// PhoneRequired 需要绑定手机
|
||||
func PhoneRequired() Response {
|
||||
return Response{
|
||||
Code: CodePhoneRequired,
|
||||
Msg: "此功能需要绑定手机后使用",
|
||||
}
|
||||
}
|
||||
|
||||
// User 用户序列化器
|
||||
type User struct {
|
||||
ID string `json:"id"`
|
||||
Email string `json:"user_name"`
|
||||
Nickname string `json:"nickname"`
|
||||
Status int `json:"status"`
|
||||
Avatar string `json:"avatar"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
PreferredTheme string `json:"preferred_theme"`
|
||||
Score int `json:"score"`
|
||||
Anonymous bool `json:"anonymous"`
|
||||
Group group `json:"group"`
|
||||
Tags []tag `json:"tags"`
|
||||
}
|
||||
|
||||
type group struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
AllowShare bool `json:"allowShare"`
|
||||
AllowRemoteDownload bool `json:"allowRemoteDownload"`
|
||||
AllowArchiveDownload bool `json:"allowArchiveDownload"`
|
||||
ShareFreeEnabled bool `json:"shareFree"`
|
||||
ShareDownload bool `json:"shareDownload"`
|
||||
CompressEnabled bool `json:"compress"`
|
||||
WebDAVEnabled bool `json:"webdav"`
|
||||
RelocateEnabled bool `json:"relocate"`
|
||||
SourceBatchSize int `json:"sourceBatch"`
|
||||
SelectNode bool `json:"selectNode"`
|
||||
AdvanceDelete bool `json:"advanceDelete"`
|
||||
AllowWebDAVProxy bool `json:"allowWebDAVProxy"`
|
||||
}
|
||||
|
||||
type tag struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Icon string `json:"icon"`
|
||||
Color string `json:"color"`
|
||||
Type int `json:"type"`
|
||||
Expression string `json:"expression"`
|
||||
}
|
||||
|
||||
type storage struct {
|
||||
Used uint64 `json:"used"`
|
||||
Free uint64 `json:"free"`
|
||||
Total uint64 `json:"total"`
|
||||
}
|
||||
|
||||
// WebAuthnCredentials 外部验证器凭证
|
||||
type WebAuthnCredentials struct {
|
||||
ID []byte `json:"id"`
|
||||
FingerPrint string `json:"fingerprint"`
|
||||
}
|
||||
|
||||
// BuildWebAuthnList 构建设置页面凭证列表
|
||||
func BuildWebAuthnList(credentials []webauthn.Credential) []WebAuthnCredentials {
|
||||
res := make([]WebAuthnCredentials, 0, len(credentials))
|
||||
for _, v := range credentials {
|
||||
credential := WebAuthnCredentials{
|
||||
ID: v.ID,
|
||||
FingerPrint: fmt.Sprintf("% X", v.Authenticator.AAGUID),
|
||||
}
|
||||
res = append(res, credential)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// BuildUser 序列化用户
|
||||
func BuildUser(user model.User) User {
|
||||
tags, _ := model.GetTagsByUID(user.ID)
|
||||
return User{
|
||||
ID: hashid.HashID(user.ID, hashid.UserID),
|
||||
Email: user.Email,
|
||||
Nickname: user.Nick,
|
||||
Status: user.Status,
|
||||
Avatar: user.Avatar,
|
||||
CreatedAt: user.CreatedAt,
|
||||
PreferredTheme: user.OptionsSerialized.PreferredTheme,
|
||||
Score: user.Score,
|
||||
Anonymous: user.IsAnonymous(),
|
||||
Group: group{
|
||||
ID: user.GroupID,
|
||||
Name: user.Group.Name,
|
||||
AllowShare: user.Group.ShareEnabled,
|
||||
AllowRemoteDownload: user.Group.OptionsSerialized.Aria2,
|
||||
AllowArchiveDownload: user.Group.OptionsSerialized.ArchiveDownload,
|
||||
ShareFreeEnabled: user.Group.OptionsSerialized.ShareFree,
|
||||
ShareDownload: user.Group.OptionsSerialized.ShareDownload,
|
||||
CompressEnabled: user.Group.OptionsSerialized.ArchiveTask,
|
||||
WebDAVEnabled: user.Group.WebDAVEnabled,
|
||||
AllowWebDAVProxy: user.Group.OptionsSerialized.WebDAVProxy,
|
||||
RelocateEnabled: user.Group.OptionsSerialized.Relocate,
|
||||
SourceBatchSize: user.Group.OptionsSerialized.SourceBatchSize,
|
||||
SelectNode: user.Group.OptionsSerialized.SelectNode,
|
||||
AdvanceDelete: user.Group.OptionsSerialized.AdvanceDelete,
|
||||
},
|
||||
Tags: buildTagRes(tags),
|
||||
}
|
||||
}
|
||||
|
||||
// BuildUserResponse 序列化用户响应
|
||||
func BuildUserResponse(user model.User) Response {
|
||||
return Response{
|
||||
Data: BuildUser(user),
|
||||
}
|
||||
}
|
||||
|
||||
// BuildUserStorageResponse 序列化用户存储概况响应
|
||||
func BuildUserStorageResponse(user model.User) Response {
|
||||
total := user.Group.MaxStorage + user.GetAvailablePackSize()
|
||||
storageResp := storage{
|
||||
Used: user.Storage,
|
||||
Free: total - user.Storage,
|
||||
Total: total,
|
||||
}
|
||||
|
||||
if total < user.Storage {
|
||||
storageResp.Free = 0
|
||||
}
|
||||
|
||||
return Response{
|
||||
Data: storageResp,
|
||||
}
|
||||
}
|
||||
|
||||
// buildTagRes 构建标签列表
|
||||
func buildTagRes(tags []model.Tag) []tag {
|
||||
res := make([]tag, 0, len(tags))
|
||||
for i := 0; i < len(tags); i++ {
|
||||
newTag := tag{
|
||||
ID: hashid.HashID(tags[i].ID, hashid.TagID),
|
||||
Name: tags[i].Name,
|
||||
Icon: tags[i].Icon,
|
||||
Color: tags[i].Color,
|
||||
Type: tags[i].Type,
|
||||
}
|
||||
if newTag.Type != 0 {
|
||||
newTag.Expression = tags[i].Expression
|
||||
|
||||
}
|
||||
res = append(res, newTag)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
158
pkg/serializer/vas.go
Normal file
158
pkg/serializer/vas.go
Normal file
@ -0,0 +1,158 @@
|
||||
package serializer
|
||||
|
||||
import (
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/hashid"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||
"time"
|
||||
)
|
||||
|
||||
type quota struct {
|
||||
Base uint64 `json:"base"`
|
||||
Pack uint64 `json:"pack"`
|
||||
Used uint64 `json:"used"`
|
||||
Total uint64 `json:"total"`
|
||||
Packs []storagePacks `json:"packs"`
|
||||
}
|
||||
|
||||
type storagePacks struct {
|
||||
Name string `json:"name"`
|
||||
Size uint64 `json:"size"`
|
||||
ActivateDate time.Time `json:"activate_date"`
|
||||
Expiration int `json:"expiration"`
|
||||
ExpirationDate time.Time `json:"expiration_date"`
|
||||
}
|
||||
|
||||
// MountedFolders 已挂载的目录
|
||||
type MountedFolders struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
PolicyName string `json:"policy_name"`
|
||||
}
|
||||
|
||||
type policyOptions struct {
|
||||
Name string `json:"name"`
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
type nodeOptions struct {
|
||||
Name string `json:"name"`
|
||||
ID uint `json:"id"`
|
||||
}
|
||||
|
||||
// BuildPolicySettingRes 构建存储策略选项选择
|
||||
func BuildPolicySettingRes(policies []model.Policy) Response {
|
||||
options := make([]policyOptions, 0, len(policies))
|
||||
for _, policy := range policies {
|
||||
options = append(options, policyOptions{
|
||||
Name: policy.Name,
|
||||
ID: hashid.HashID(policy.ID, hashid.PolicyID),
|
||||
})
|
||||
}
|
||||
|
||||
return Response{
|
||||
Data: options,
|
||||
}
|
||||
}
|
||||
|
||||
// BuildMountedFolderRes 构建已挂载目录响应,list为当前用户可用存储策略ID
|
||||
func BuildMountedFolderRes(folders []model.Folder, list []uint) []MountedFolders {
|
||||
res := make([]MountedFolders, 0, len(folders))
|
||||
for _, folder := range folders {
|
||||
single := MountedFolders{
|
||||
ID: hashid.HashID(folder.ID, hashid.FolderID),
|
||||
Name: folder.Name,
|
||||
PolicyName: "[Invalid Policy]",
|
||||
}
|
||||
if policy, err := model.GetPolicyByID(folder.PolicyID); err == nil && util.ContainsUint(list, policy.ID) {
|
||||
single.PolicyName = policy.Name
|
||||
}
|
||||
|
||||
res = append(res, single)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// BuildUserQuotaResponse 序列化用户存储配额概况响应
|
||||
func BuildUserQuotaResponse(user *model.User, packs []model.StoragePack) Response {
|
||||
packSize := user.GetAvailablePackSize()
|
||||
res := quota{
|
||||
Base: user.Group.MaxStorage,
|
||||
Pack: packSize,
|
||||
Used: user.Storage,
|
||||
Total: packSize + user.Group.MaxStorage,
|
||||
Packs: make([]storagePacks, 0, len(packs)),
|
||||
}
|
||||
for _, pack := range packs {
|
||||
res.Packs = append(res.Packs, storagePacks{
|
||||
Name: pack.Name,
|
||||
Size: pack.Size,
|
||||
ActivateDate: *pack.ActiveTime,
|
||||
Expiration: int(pack.ExpiredTime.Sub(*pack.ActiveTime).Seconds()),
|
||||
ExpirationDate: *pack.ExpiredTime,
|
||||
})
|
||||
}
|
||||
|
||||
return Response{
|
||||
Data: res,
|
||||
}
|
||||
}
|
||||
|
||||
// PackProduct 容量包商品
|
||||
type PackProduct struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Size uint64 `json:"size"`
|
||||
Time int64 `json:"time"`
|
||||
Price int `json:"price"`
|
||||
Score int `json:"score"`
|
||||
}
|
||||
|
||||
// GroupProducts 用户组商品
|
||||
type GroupProducts struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
GroupID uint `json:"group_id"`
|
||||
Time int64 `json:"time"`
|
||||
Price int `json:"price"`
|
||||
Score int `json:"score"`
|
||||
Des []string `json:"des"`
|
||||
Highlight bool `json:"highlight"`
|
||||
}
|
||||
|
||||
// BuildProductResponse 构建增值服务商品响应
|
||||
func BuildProductResponse(groups []GroupProducts, packs []PackProduct,
|
||||
wechat, alipay, payjs, custom bool, customName string, scorePrice int) Response {
|
||||
// 隐藏响应中的用户组ID
|
||||
for i := 0; i < len(groups); i++ {
|
||||
groups[i].GroupID = 0
|
||||
}
|
||||
return Response{
|
||||
Data: map[string]interface{}{
|
||||
"packs": packs,
|
||||
"groups": groups,
|
||||
"alipay": alipay,
|
||||
"wechat": wechat,
|
||||
"payjs": payjs,
|
||||
"custom": custom,
|
||||
"custom_name": customName,
|
||||
"score_price": scorePrice,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// BuildNodeOptionRes 构建可用节点列表响应
|
||||
func BuildNodeOptionRes(nodes []*model.Node) Response {
|
||||
options := make([]nodeOptions, 0, len(nodes))
|
||||
for _, node := range nodes {
|
||||
options = append(options, nodeOptions{
|
||||
Name: node.Name,
|
||||
ID: node.ID,
|
||||
})
|
||||
}
|
||||
|
||||
return Response{
|
||||
Data: options,
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user