init
This commit is contained in:
71
service/admin/aria2.go
Normal file
71
service/admin/aria2.go
Normal file
@ -0,0 +1,71 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/aria2"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/auth"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/request"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
||||
)
|
||||
|
||||
// Aria2TestService aria2连接测试服务
|
||||
type Aria2TestService struct {
|
||||
Server string `json:"server"`
|
||||
RPC string `json:"rpc" binding:"required"`
|
||||
Secret string `json:"secret"`
|
||||
Token string `json:"token"`
|
||||
Type model.ModelType `json:"type"`
|
||||
}
|
||||
|
||||
// Test 测试aria2连接
|
||||
func (service *Aria2TestService) TestMaster() serializer.Response {
|
||||
res, err := aria2.TestRPCConnection(service.RPC, service.Token, 5)
|
||||
if err != nil {
|
||||
return serializer.ParamErr("Failed to connect to RPC server: "+err.Error(), err)
|
||||
}
|
||||
|
||||
if res.Version == "" {
|
||||
return serializer.ParamErr("RPC server returns unexpected response", nil)
|
||||
}
|
||||
|
||||
return serializer.Response{Data: res.Version}
|
||||
}
|
||||
|
||||
func (service *Aria2TestService) TestSlave() serializer.Response {
|
||||
slave, err := url.Parse(service.Server)
|
||||
if err != nil {
|
||||
return serializer.ParamErr("Cannot parse slave server URL, "+err.Error(), nil)
|
||||
}
|
||||
|
||||
controller, _ := url.Parse("/api/v3/slave/ping/aria2")
|
||||
|
||||
// 请求正文
|
||||
service.Type = model.MasterNodeType
|
||||
bodyByte, _ := json.Marshal(service)
|
||||
|
||||
r := request.NewClient()
|
||||
res, err := r.Request(
|
||||
"POST",
|
||||
slave.ResolveReference(controller).String(),
|
||||
bytes.NewReader(bodyByte),
|
||||
request.WithTimeout(time.Duration(10)*time.Second),
|
||||
request.WithCredential(
|
||||
auth.HMACAuth{SecretKey: []byte(service.Secret)},
|
||||
int64(model.GetIntSetting("slave_api_timeout", 60)),
|
||||
),
|
||||
).DecodeResponse()
|
||||
if err != nil {
|
||||
return serializer.ParamErr("Failed to connect to slave node, "+err.Error(), nil)
|
||||
}
|
||||
|
||||
if res.Code != 0 {
|
||||
return serializer.ParamErr("Successfully connected to slave, but slave returns: "+res.Msg, nil)
|
||||
}
|
||||
|
||||
return serializer.Response{Data: res.Data.(string)}
|
||||
}
|
208
service/admin/file.go
Normal file
208
service/admin/file.go
Normal file
@ -0,0 +1,208 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
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/serializer"
|
||||
"github.com/cloudreve/Cloudreve/v3/service/explorer"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// FileService 文件ID服务
|
||||
type FileService struct {
|
||||
ID uint `uri:"id" json:"id" binding:"required"`
|
||||
}
|
||||
|
||||
// FileBatchService 文件批量操作服务
|
||||
type FileBatchService struct {
|
||||
ID []uint `json:"id" binding:"min=1"`
|
||||
Force bool `json:"force"`
|
||||
UnlinkOnly bool `json:"unlink"`
|
||||
}
|
||||
|
||||
// ListFolderService 列目录结构
|
||||
type ListFolderService struct {
|
||||
Path string `uri:"path" binding:"required,max=65535"`
|
||||
ID uint `uri:"id" binding:"required"`
|
||||
Type string `uri:"type" binding:"eq=policy|eq=user"`
|
||||
}
|
||||
|
||||
// List 列出指定路径下的目录
|
||||
func (service *ListFolderService) List(c *gin.Context) serializer.Response {
|
||||
if service.Type == "policy" {
|
||||
// 列取存储策略中的目录
|
||||
policy, err := model.GetPolicyByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotExist, "", err)
|
||||
}
|
||||
|
||||
// 创建文件系统
|
||||
fs, err := filesystem.NewAnonymousFileSystem()
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
// 列取存储策略中的文件
|
||||
fs.Policy = &policy
|
||||
res, err := fs.ListPhysical(c.Request.Context(), service.Path)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeListFilesError, "", err)
|
||||
}
|
||||
|
||||
return serializer.Response{
|
||||
Data: serializer.BuildObjectList(0, res, nil),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 列取用户空间目录
|
||||
// 查找用户
|
||||
user, err := model.GetUserByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeUserNotFound, "", err)
|
||||
}
|
||||
|
||||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystem(&user)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeCreateFSError, "", err)
|
||||
}
|
||||
defer fs.Recycle()
|
||||
|
||||
// 列取目录
|
||||
res, err := fs.List(c.Request.Context(), service.Path, nil)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeListFilesError, "", err)
|
||||
}
|
||||
|
||||
return serializer.Response{
|
||||
Data: serializer.BuildObjectList(0, res, nil),
|
||||
}
|
||||
}
|
||||
|
||||
// Delete 删除文件
|
||||
func (service *FileBatchService) Delete(c *gin.Context) serializer.Response {
|
||||
files, err := model.GetFilesByIDs(service.ID, 0)
|
||||
if err != nil {
|
||||
return serializer.DBErr("Failed to list files for deleting", err)
|
||||
}
|
||||
|
||||
// 根据用户分组
|
||||
userFile := make(map[uint][]model.File)
|
||||
for i := 0; i < len(files); i++ {
|
||||
if _, ok := userFile[files[i].UserID]; !ok {
|
||||
userFile[files[i].UserID] = []model.File{}
|
||||
}
|
||||
userFile[files[i].UserID] = append(userFile[files[i].UserID], files[i])
|
||||
}
|
||||
|
||||
// 异步执行删除
|
||||
go func(files map[uint][]model.File) {
|
||||
for uid, file := range files {
|
||||
var (
|
||||
fs *filesystem.FileSystem
|
||||
err error
|
||||
)
|
||||
user, err := model.GetUserByID(uid)
|
||||
if err != nil {
|
||||
fs, err = filesystem.NewAnonymousFileSystem()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
fs, err = filesystem.NewFileSystem(&user)
|
||||
if err != nil {
|
||||
fs.Recycle()
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// 汇总文件ID
|
||||
ids := make([]uint, 0, len(file))
|
||||
for i := 0; i < len(file); i++ {
|
||||
ids = append(ids, file[i].ID)
|
||||
}
|
||||
|
||||
// 执行删除
|
||||
fs.Delete(context.Background(), []uint{}, ids, service.Force, service.UnlinkOnly)
|
||||
fs.Recycle()
|
||||
}
|
||||
}(userFile)
|
||||
|
||||
// 分组执行删除
|
||||
return serializer.Response{}
|
||||
|
||||
}
|
||||
|
||||
// Get 预览文件
|
||||
func (service *FileService) Get(c *gin.Context) serializer.Response {
|
||||
file, err := model.GetFilesByIDs([]uint{service.ID}, 0)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeFileNotFound, "", err)
|
||||
}
|
||||
|
||||
ctx := context.WithValue(context.Background(), fsctx.FileModelCtx, &file[0])
|
||||
var subService explorer.FileIDService
|
||||
res := subService.PreviewContent(ctx, c, false)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// Files 列出文件
|
||||
func (service *AdminListService) Files() serializer.Response {
|
||||
var res []model.File
|
||||
total := 0
|
||||
|
||||
tx := model.DB.Model(&model.File{})
|
||||
if service.OrderBy != "" {
|
||||
tx = tx.Order(service.OrderBy)
|
||||
}
|
||||
|
||||
for k, v := range service.Conditions {
|
||||
tx = tx.Where(k+" = ?", v)
|
||||
}
|
||||
|
||||
if len(service.Searches) > 0 {
|
||||
search := ""
|
||||
for k, v := range service.Searches {
|
||||
search += k + " like '%" + v + "%' OR "
|
||||
}
|
||||
search = strings.TrimSuffix(search, " OR ")
|
||||
tx = tx.Where(search)
|
||||
}
|
||||
|
||||
// 计算总数用于分页
|
||||
tx.Count(&total)
|
||||
|
||||
// 查询记录
|
||||
tx.Limit(service.PageSize).Offset((service.Page - 1) * service.PageSize).Find(&res)
|
||||
|
||||
// 查询对应用户
|
||||
users := make(map[uint]model.User)
|
||||
for _, file := range res {
|
||||
users[file.UserID] = model.User{}
|
||||
}
|
||||
|
||||
userIDs := make([]uint, 0, len(users))
|
||||
for k := range users {
|
||||
userIDs = append(userIDs, k)
|
||||
}
|
||||
|
||||
var userList []model.User
|
||||
model.DB.Where("id in (?)", userIDs).Find(&userList)
|
||||
|
||||
for _, v := range userList {
|
||||
users[v.ID] = v
|
||||
}
|
||||
|
||||
return serializer.Response{Data: map[string]interface{}{
|
||||
"total": total,
|
||||
"items": res,
|
||||
"users": users,
|
||||
}}
|
||||
}
|
117
service/admin/group.go
Normal file
117
service/admin/group.go
Normal file
@ -0,0 +1,117 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// AddGroupService 用户组添加服务
|
||||
type AddGroupService struct {
|
||||
Group model.Group `json:"group" binding:"required"`
|
||||
}
|
||||
|
||||
// GroupService 用户组ID服务
|
||||
type GroupService struct {
|
||||
ID uint `uri:"id" json:"id" binding:"required"`
|
||||
}
|
||||
|
||||
// Get 获取用户组详情
|
||||
func (service *GroupService) Get() serializer.Response {
|
||||
group, err := model.GetGroupByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeGroupNotFound, "", err)
|
||||
}
|
||||
|
||||
return serializer.Response{Data: group}
|
||||
}
|
||||
|
||||
// Delete 删除用户组
|
||||
func (service *GroupService) Delete() serializer.Response {
|
||||
// 查找用户组
|
||||
group, err := model.GetGroupByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeGroupNotFound, "", err)
|
||||
}
|
||||
|
||||
// 是否为系统用户组
|
||||
if group.ID <= 3 {
|
||||
return serializer.Err(serializer.CodeInvalidActionOnSystemGroup, "", err)
|
||||
}
|
||||
|
||||
// 检查是否有用户使用
|
||||
total := 0
|
||||
row := model.DB.Model(&model.User{}).Where("group_id = ?", service.ID).
|
||||
Select("count(id)").Row()
|
||||
row.Scan(&total)
|
||||
if total > 0 {
|
||||
return serializer.Err(serializer.CodeGroupUsedByUser, strconv.Itoa(total), nil)
|
||||
}
|
||||
|
||||
model.DB.Delete(&group)
|
||||
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// Add 添加用户组
|
||||
func (service *AddGroupService) Add() serializer.Response {
|
||||
if service.Group.ID > 0 {
|
||||
if err := model.DB.Save(&service.Group).Error; err != nil {
|
||||
return serializer.DBErr("Failed to save group record", err)
|
||||
}
|
||||
} else {
|
||||
if err := model.DB.Create(&service.Group).Error; err != nil {
|
||||
return serializer.DBErr("Failed to create group record", err)
|
||||
}
|
||||
}
|
||||
|
||||
return serializer.Response{Data: service.Group.ID}
|
||||
}
|
||||
|
||||
// Groups 列出用户组
|
||||
func (service *AdminListService) Groups() serializer.Response {
|
||||
var res []model.Group
|
||||
total := 0
|
||||
|
||||
tx := model.DB.Model(&model.Group{})
|
||||
if service.OrderBy != "" {
|
||||
tx = tx.Order(service.OrderBy)
|
||||
}
|
||||
|
||||
for k, v := range service.Conditions {
|
||||
tx = tx.Where(k+" = ?", v)
|
||||
}
|
||||
|
||||
// 计算总数用于分页
|
||||
tx.Count(&total)
|
||||
|
||||
// 查询记录
|
||||
tx.Limit(service.PageSize).Offset((service.Page - 1) * service.PageSize).Find(&res)
|
||||
|
||||
// 统计每个用户组的用户总数
|
||||
statics := make(map[uint]int, len(res))
|
||||
for i := 0; i < len(res); i++ {
|
||||
total := 0
|
||||
row := model.DB.Model(&model.User{}).Where("group_id = ?", res[i].ID).
|
||||
Select("count(id)").Row()
|
||||
row.Scan(&total)
|
||||
statics[res[i].ID] = total
|
||||
}
|
||||
|
||||
// 汇总用户组存储策略
|
||||
policies := make(map[uint]model.Policy)
|
||||
for i := 0; i < len(res); i++ {
|
||||
for _, p := range res[i].PolicyList {
|
||||
if _, ok := policies[p]; !ok {
|
||||
policies[p], _ = model.GetPolicyByID(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return serializer.Response{Data: map[string]interface{}{
|
||||
"total": total,
|
||||
"items": res,
|
||||
"statics": statics,
|
||||
"policies": policies,
|
||||
}}
|
||||
}
|
22
service/admin/list.go
Normal file
22
service/admin/list.go
Normal file
@ -0,0 +1,22 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
||||
)
|
||||
|
||||
// AdminListService 仪表盘列条目服务
|
||||
type AdminListService struct {
|
||||
Page int `json:"page" binding:"min=1,required"`
|
||||
PageSize int `json:"page_size" binding:"min=1,required"`
|
||||
OrderBy string `json:"order_by"`
|
||||
Conditions map[string]string `form:"conditions"`
|
||||
Searches map[string]string `form:"searches"`
|
||||
}
|
||||
|
||||
// GroupList 获取用户组列表
|
||||
func (service *NoParamService) GroupList() serializer.Response {
|
||||
var res []model.Group
|
||||
model.DB.Model(&model.Group{}).Find(&res)
|
||||
return serializer.Response{Data: res}
|
||||
}
|
142
service/admin/node.go
Normal file
142
service/admin/node.go
Normal file
@ -0,0 +1,142 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/cluster"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AddNodeService 节点添加服务
|
||||
type AddNodeService struct {
|
||||
Node model.Node `json:"node" binding:"required"`
|
||||
}
|
||||
|
||||
// Add 添加节点
|
||||
func (service *AddNodeService) Add() serializer.Response {
|
||||
if service.Node.ID > 0 {
|
||||
if err := model.DB.Save(&service.Node).Error; err != nil {
|
||||
return serializer.DBErr("Failed to save node record", err)
|
||||
}
|
||||
} else {
|
||||
if err := model.DB.Create(&service.Node).Error; err != nil {
|
||||
return serializer.DBErr("Failed to create node record", err)
|
||||
}
|
||||
}
|
||||
|
||||
if service.Node.Status == model.NodeActive {
|
||||
cluster.Default.Add(&service.Node)
|
||||
}
|
||||
|
||||
return serializer.Response{Data: service.Node.ID}
|
||||
}
|
||||
|
||||
// Nodes 列出从机节点
|
||||
func (service *AdminListService) Nodes() serializer.Response {
|
||||
var res []model.Node
|
||||
total := 0
|
||||
|
||||
tx := model.DB.Model(&model.Node{})
|
||||
if service.OrderBy != "" {
|
||||
tx = tx.Order(service.OrderBy)
|
||||
}
|
||||
|
||||
for k, v := range service.Conditions {
|
||||
tx = tx.Where(k+" = ?", v)
|
||||
}
|
||||
|
||||
if len(service.Searches) > 0 {
|
||||
search := ""
|
||||
for k, v := range service.Searches {
|
||||
search += k + " like '%" + v + "%' OR "
|
||||
}
|
||||
search = strings.TrimSuffix(search, " OR ")
|
||||
tx = tx.Where(search)
|
||||
}
|
||||
|
||||
// 计算总数用于分页
|
||||
tx.Count(&total)
|
||||
|
||||
// 查询记录
|
||||
tx.Limit(service.PageSize).Offset((service.Page - 1) * service.PageSize).Find(&res)
|
||||
|
||||
isActive := make(map[uint]bool)
|
||||
for i := 0; i < len(res); i++ {
|
||||
if node := cluster.Default.GetNodeByID(res[i].ID); node != nil {
|
||||
isActive[res[i].ID] = node.IsActive()
|
||||
}
|
||||
}
|
||||
|
||||
return serializer.Response{Data: map[string]interface{}{
|
||||
"total": total,
|
||||
"items": res,
|
||||
"active": isActive,
|
||||
}}
|
||||
}
|
||||
|
||||
// ToggleNodeService 开关节点服务
|
||||
type ToggleNodeService struct {
|
||||
ID uint `uri:"id"`
|
||||
Desired model.NodeStatus `uri:"desired"`
|
||||
}
|
||||
|
||||
// Toggle 开关节点
|
||||
func (service *ToggleNodeService) Toggle() serializer.Response {
|
||||
node, err := model.GetNodeByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.DBErr("Node not found", err)
|
||||
}
|
||||
|
||||
// 是否为系统节点
|
||||
if node.ID <= 1 {
|
||||
return serializer.Err(serializer.CodeInvalidActionOnSystemNode, "", err)
|
||||
}
|
||||
|
||||
if err = node.SetStatus(service.Desired); err != nil {
|
||||
return serializer.DBErr("Failed to change node status", err)
|
||||
}
|
||||
|
||||
if service.Desired == model.NodeActive {
|
||||
cluster.Default.Add(&node)
|
||||
} else {
|
||||
cluster.Default.Delete(node.ID)
|
||||
}
|
||||
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// NodeService 节点ID服务
|
||||
type NodeService struct {
|
||||
ID uint `uri:"id" json:"id" binding:"required"`
|
||||
}
|
||||
|
||||
// Delete 删除节点
|
||||
func (service *NodeService) Delete() serializer.Response {
|
||||
// 查找用户组
|
||||
node, err := model.GetNodeByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.DBErr("Node record not found", err)
|
||||
}
|
||||
|
||||
// 是否为系统节点
|
||||
if node.ID <= 1 {
|
||||
return serializer.Err(serializer.CodeInvalidActionOnSystemNode, "", err)
|
||||
}
|
||||
|
||||
cluster.Default.Delete(node.ID)
|
||||
if err := model.DB.Delete(&node).Error; err != nil {
|
||||
return serializer.DBErr("Failed to delete node record", err)
|
||||
}
|
||||
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// Get 获取节点详情
|
||||
func (service *NodeService) Get() serializer.Response {
|
||||
node, err := model.GetNodeByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.DBErr("Node not exist", err)
|
||||
}
|
||||
|
||||
return serializer.Response{Data: node}
|
||||
}
|
75
service/admin/order.go
Normal file
75
service/admin/order.go
Normal file
@ -0,0 +1,75 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// OrderBatchService 订单批量操作服务
|
||||
type OrderBatchService struct {
|
||||
ID []uint `json:"id" binding:"min=1"`
|
||||
}
|
||||
|
||||
// Delete 删除订单
|
||||
func (service *OrderBatchService) Delete(c *gin.Context) serializer.Response {
|
||||
if err := model.DB.Where("id in (?)", service.ID).Delete(&model.Order{}).Error; err != nil {
|
||||
return serializer.DBErr("Failed to delete order records.", err)
|
||||
}
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// Orders 列出订单
|
||||
func (service *AdminListService) Orders() serializer.Response {
|
||||
var res []model.Order
|
||||
total := 0
|
||||
|
||||
tx := model.DB.Model(&model.Order{})
|
||||
if service.OrderBy != "" {
|
||||
tx = tx.Order(service.OrderBy)
|
||||
}
|
||||
|
||||
for k, v := range service.Conditions {
|
||||
tx = tx.Where(k+" = ?", v)
|
||||
}
|
||||
|
||||
if len(service.Searches) > 0 {
|
||||
search := ""
|
||||
for k, v := range service.Searches {
|
||||
search += k + " like '%" + v + "%' OR "
|
||||
}
|
||||
search = strings.TrimSuffix(search, " OR ")
|
||||
tx = tx.Where(search)
|
||||
}
|
||||
|
||||
// 计算总数用于分页
|
||||
tx.Count(&total)
|
||||
|
||||
// 查询记录
|
||||
tx.Limit(service.PageSize).Offset((service.Page - 1) * service.PageSize).Find(&res)
|
||||
|
||||
// 查询对应用户,同时计算HashID
|
||||
users := make(map[uint]model.User)
|
||||
for _, file := range res {
|
||||
users[file.UserID] = model.User{}
|
||||
}
|
||||
|
||||
userIDs := make([]uint, 0, len(users))
|
||||
for k := range users {
|
||||
userIDs = append(userIDs, k)
|
||||
}
|
||||
|
||||
var userList []model.User
|
||||
model.DB.Where("id in (?)", userIDs).Find(&userList)
|
||||
|
||||
for _, v := range userList {
|
||||
users[v.ID] = v
|
||||
}
|
||||
|
||||
return serializer.Response{Data: map[string]interface{}{
|
||||
"total": total,
|
||||
"items": res,
|
||||
"users": users,
|
||||
}}
|
||||
}
|
360
service/admin/policy.go
Normal file
360
service/admin/policy.go
Normal file
@ -0,0 +1,360 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver/googledrive"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/auth"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver/cos"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver/onedrive"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver/oss"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver/s3"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/request"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
cossdk "github.com/tencentyun/cos-go-sdk-v5"
|
||||
)
|
||||
|
||||
// PathTestService 本地路径测试服务
|
||||
type PathTestService struct {
|
||||
Path string `json:"path" binding:"required"`
|
||||
}
|
||||
|
||||
// SlaveTestService 从机测试服务
|
||||
type SlaveTestService struct {
|
||||
Secret string `json:"secret" binding:"required"`
|
||||
Server string `json:"server" binding:"required"`
|
||||
}
|
||||
|
||||
// SlavePingService 从机相应ping
|
||||
type SlavePingService struct {
|
||||
Callback string `json:"callback" binding:"required"`
|
||||
}
|
||||
|
||||
// AddPolicyService 存储策略添加服务
|
||||
type AddPolicyService struct {
|
||||
Policy model.Policy `json:"policy" binding:"required"`
|
||||
}
|
||||
|
||||
// PolicyService 存储策略ID服务
|
||||
type PolicyService struct {
|
||||
ID uint `uri:"id" json:"id" binding:"required"`
|
||||
Region string `json:"region"`
|
||||
}
|
||||
|
||||
// Delete 删除存储策略
|
||||
func (service *PolicyService) Delete() serializer.Response {
|
||||
// 禁止删除默认策略
|
||||
if service.ID == 1 {
|
||||
return serializer.Err(serializer.CodeDeleteDefaultPolicy, "", nil)
|
||||
}
|
||||
|
||||
policy, err := model.GetPolicyByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotExist, "", err)
|
||||
}
|
||||
|
||||
// 检查是否有文件使用
|
||||
total := 0
|
||||
row := model.DB.Model(&model.File{}).Where("policy_id = ?", service.ID).
|
||||
Select("count(id)").Row()
|
||||
row.Scan(&total)
|
||||
if total > 0 {
|
||||
return serializer.Err(serializer.CodePolicyUsedByFiles, strconv.Itoa(total), nil)
|
||||
}
|
||||
|
||||
// 检查用户组使用
|
||||
var groups []model.Group
|
||||
model.DB.Model(&model.Group{}).Where(
|
||||
"policies like ? OR policies like ? OR policies like ? OR policies like ?",
|
||||
fmt.Sprintf("[%d,%%", service.ID),
|
||||
fmt.Sprintf("%%,%d]", service.ID),
|
||||
fmt.Sprintf("%%,%d,%%", service.ID),
|
||||
fmt.Sprintf("%%[%d]%%", service.ID),
|
||||
).Find(&groups)
|
||||
|
||||
if len(groups) > 0 {
|
||||
return serializer.Err(serializer.CodePolicyUsedByGroups, strconv.Itoa(len(groups)), nil)
|
||||
}
|
||||
|
||||
model.DB.Delete(&policy)
|
||||
policy.ClearCache()
|
||||
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// Get 获取存储策略详情
|
||||
func (service *PolicyService) Get() serializer.Response {
|
||||
policy, err := model.GetPolicyByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotExist, "", err)
|
||||
}
|
||||
|
||||
return serializer.Response{Data: policy}
|
||||
}
|
||||
|
||||
// GetOAuth 获取 OneDrive OAuth 地址
|
||||
func (service *PolicyService) GetOAuth(c *gin.Context, policyType string) serializer.Response {
|
||||
policy, err := model.GetPolicyByID(service.ID)
|
||||
if err != nil || policy.Type != policyType {
|
||||
return serializer.Err(serializer.CodePolicyNotExist, "", nil)
|
||||
}
|
||||
|
||||
util.SetSession(c, map[string]interface{}{
|
||||
policyType + "_oauth_policy": policy.ID,
|
||||
})
|
||||
|
||||
var redirect string
|
||||
switch policy.Type {
|
||||
case "onedrive":
|
||||
client, err := onedrive.NewClient(&policy)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "Failed to initialize OneDrive client", err)
|
||||
}
|
||||
|
||||
redirect = client.OAuthURL(context.Background(), []string{
|
||||
"offline_access",
|
||||
"files.readwrite.all",
|
||||
})
|
||||
case "googledrive":
|
||||
client, err := googledrive.NewClient(&policy)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "Failed to initialize Google Drive client", err)
|
||||
}
|
||||
|
||||
redirect = client.OAuthURL(context.Background(), googledrive.RequiredScope)
|
||||
}
|
||||
|
||||
// Delete token cache
|
||||
cache.Deletes([]string{policy.BucketName}, policyType+"_")
|
||||
|
||||
return serializer.Response{Data: redirect}
|
||||
}
|
||||
|
||||
// AddSCF 创建回调云函数
|
||||
func (service *PolicyService) AddSCF() serializer.Response {
|
||||
policy, err := model.GetPolicyByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotExist, "", nil)
|
||||
}
|
||||
|
||||
if err := cos.CreateSCF(&policy, service.Region); err != nil {
|
||||
return serializer.ParamErr("Failed to create SCF function", err)
|
||||
}
|
||||
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// AddCORS 创建跨域策略
|
||||
func (service *PolicyService) AddCORS() serializer.Response {
|
||||
policy, err := model.GetPolicyByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotExist, "", nil)
|
||||
}
|
||||
|
||||
switch policy.Type {
|
||||
case "oss":
|
||||
handler, err := oss.NewDriver(&policy)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeAddCORS, "", err)
|
||||
}
|
||||
if err := handler.CORS(); err != nil {
|
||||
return serializer.Err(serializer.CodeAddCORS, "", err)
|
||||
}
|
||||
case "cos":
|
||||
u, _ := url.Parse(policy.Server)
|
||||
b := &cossdk.BaseURL{BucketURL: u}
|
||||
handler := cos.Driver{
|
||||
Policy: &policy,
|
||||
HTTPClient: request.NewClient(),
|
||||
Client: cossdk.NewClient(b, &http.Client{
|
||||
Transport: &cossdk.AuthorizationTransport{
|
||||
SecretID: policy.AccessKey,
|
||||
SecretKey: policy.SecretKey,
|
||||
},
|
||||
}),
|
||||
}
|
||||
if err := handler.CORS(); err != nil {
|
||||
return serializer.Err(serializer.CodeAddCORS, "", err)
|
||||
}
|
||||
case "s3":
|
||||
handler, err := s3.NewDriver(&policy)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeAddCORS, "", err)
|
||||
}
|
||||
|
||||
if err := handler.CORS(); err != nil {
|
||||
return serializer.Err(serializer.CodeAddCORS, "", err)
|
||||
}
|
||||
default:
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, "", nil)
|
||||
}
|
||||
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// Test 从机响应ping
|
||||
func (service *SlavePingService) Test() serializer.Response {
|
||||
master, err := url.Parse(service.Callback)
|
||||
if err != nil {
|
||||
return serializer.ParamErr("Failed to parse Master site url: "+err.Error(), nil)
|
||||
}
|
||||
|
||||
controller, _ := url.Parse("/api/v3/site/ping")
|
||||
|
||||
r := request.NewClient()
|
||||
res, err := r.Request(
|
||||
"GET",
|
||||
master.ResolveReference(controller).String(),
|
||||
nil,
|
||||
request.WithTimeout(time.Duration(10)*time.Second),
|
||||
).DecodeResponse()
|
||||
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeSlavePingMaster, err.Error(), nil)
|
||||
}
|
||||
|
||||
version := conf.BackendVersion
|
||||
if conf.IsPlus == "true" {
|
||||
version += "-plus"
|
||||
}
|
||||
if res.Data.(string) != version {
|
||||
return serializer.Err(serializer.CodeVersionMismatch, "Master: "+res.Data.(string)+", Slave: "+version, nil)
|
||||
}
|
||||
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// Test 测试从机通信
|
||||
func (service *SlaveTestService) Test() serializer.Response {
|
||||
slave, err := url.Parse(service.Server)
|
||||
if err != nil {
|
||||
return serializer.ParamErr("Failed to parse slave node server URL: "+err.Error(), nil)
|
||||
}
|
||||
|
||||
controller, _ := url.Parse("/api/v3/slave/ping")
|
||||
|
||||
// 请求正文
|
||||
body := map[string]string{
|
||||
"callback": model.GetSiteURL().String(),
|
||||
}
|
||||
bodyByte, _ := json.Marshal(body)
|
||||
|
||||
r := request.NewClient()
|
||||
res, err := r.Request(
|
||||
"POST",
|
||||
slave.ResolveReference(controller).String(),
|
||||
bytes.NewReader(bodyByte),
|
||||
request.WithTimeout(time.Duration(10)*time.Second),
|
||||
request.WithCredential(
|
||||
auth.HMACAuth{SecretKey: []byte(service.Secret)},
|
||||
int64(model.GetIntSetting("slave_api_timeout", 60)),
|
||||
),
|
||||
).DecodeResponse()
|
||||
if err != nil {
|
||||
return serializer.ParamErr("Failed to connect to slave node: "+err.Error(), nil)
|
||||
}
|
||||
|
||||
if res.Code != 0 {
|
||||
return serializer.ParamErr("Successfully connected to slave node, but slave returns: "+res.Msg, nil)
|
||||
}
|
||||
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// Add 添加存储策略
|
||||
func (service *AddPolicyService) Add() serializer.Response {
|
||||
if service.Policy.Type != "local" && service.Policy.Type != "remote" {
|
||||
service.Policy.DirNameRule = strings.TrimPrefix(service.Policy.DirNameRule, "/")
|
||||
}
|
||||
|
||||
if service.Policy.ID > 0 {
|
||||
if err := model.DB.Save(&service.Policy).Error; err != nil {
|
||||
return serializer.DBErr("Failed to save policy", err)
|
||||
}
|
||||
} else {
|
||||
if err := model.DB.Create(&service.Policy).Error; err != nil {
|
||||
return serializer.DBErr("Failed to create policy", err)
|
||||
}
|
||||
}
|
||||
|
||||
service.Policy.ClearCache()
|
||||
|
||||
return serializer.Response{Data: service.Policy.ID}
|
||||
}
|
||||
|
||||
// Test 测试本地路径
|
||||
func (service *PathTestService) Test() serializer.Response {
|
||||
policy := model.Policy{DirNameRule: service.Path}
|
||||
path := policy.GeneratePath(1, "/My File")
|
||||
path = filepath.Join(path, "test.txt")
|
||||
file, err := util.CreatNestedFile(util.RelativePath(path))
|
||||
if err != nil {
|
||||
return serializer.ParamErr(fmt.Sprintf("Failed to create \"%s\": %s", path, err.Error()), nil)
|
||||
}
|
||||
|
||||
file.Close()
|
||||
os.Remove(path)
|
||||
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// Policies 列出存储策略
|
||||
func (service *AdminListService) Policies() serializer.Response {
|
||||
var res []model.Policy
|
||||
total := 0
|
||||
|
||||
tx := model.DB.Model(&model.Policy{})
|
||||
if service.OrderBy != "" {
|
||||
tx = tx.Order(service.OrderBy)
|
||||
}
|
||||
|
||||
for k, v := range service.Conditions {
|
||||
tx = tx.Where(k+" = ?", v)
|
||||
}
|
||||
|
||||
// 计算总数用于分页
|
||||
tx.Count(&total)
|
||||
|
||||
// 查询记录
|
||||
tx.Limit(service.PageSize).Offset((service.Page - 1) * service.PageSize).Find(&res)
|
||||
|
||||
// 统计每个策略的文件使用
|
||||
statics := make(map[uint][2]int, len(res))
|
||||
policyIds := make([]uint, 0, len(res))
|
||||
for i := 0; i < len(res); i++ {
|
||||
policyIds = append(policyIds, res[i].ID)
|
||||
}
|
||||
|
||||
rows, _ := model.DB.Model(&model.File{}).Where("policy_id in (?)", policyIds).
|
||||
Select("policy_id,count(id),sum(size)").Group("policy_id").Rows()
|
||||
|
||||
for rows.Next() {
|
||||
policyId := uint(0)
|
||||
total := [2]int{}
|
||||
rows.Scan(&policyId, &total[0], &total[1])
|
||||
|
||||
statics[policyId] = total
|
||||
}
|
||||
|
||||
return serializer.Response{Data: map[string]interface{}{
|
||||
"total": total,
|
||||
"items": res,
|
||||
"statics": statics,
|
||||
}}
|
||||
}
|
72
service/admin/report.go
Normal file
72
service/admin/report.go
Normal file
@ -0,0 +1,72 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/hashid"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
||||
)
|
||||
|
||||
// ReportBatchService 任务批量操作服务
|
||||
type ReportBatchService struct {
|
||||
ID []uint `json:"id" binding:"min=1"`
|
||||
}
|
||||
|
||||
// Reports 批量删除举报
|
||||
func (service *ReportBatchService) Delete() serializer.Response {
|
||||
if err := model.DB.Where("id in (?)", service.ID).Delete(&model.Report{}).Error; err != nil {
|
||||
return serializer.DBErr("Failed to change report status", err)
|
||||
}
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// Reports 列出待处理举报
|
||||
func (service *AdminListService) Reports() serializer.Response {
|
||||
var res []model.Report
|
||||
total := 0
|
||||
|
||||
tx := model.DB.Model(&model.Report{})
|
||||
if service.OrderBy != "" {
|
||||
tx = tx.Order(service.OrderBy)
|
||||
}
|
||||
|
||||
for k, v := range service.Conditions {
|
||||
tx = tx.Where(k+" = ?", v)
|
||||
}
|
||||
|
||||
// 计算总数用于分页
|
||||
tx.Count(&total)
|
||||
|
||||
// 查询记录
|
||||
tx.Set("gorm:auto_preload", true).Limit(service.PageSize).Offset((service.Page - 1) * service.PageSize).Find(&res)
|
||||
|
||||
// 计算分享的 HashID
|
||||
hashIDs := make(map[uint]string, len(res))
|
||||
for _, report := range res {
|
||||
hashIDs[report.Share.ID] = hashid.HashID(report.Share.ID, hashid.ShareID)
|
||||
}
|
||||
|
||||
// 查询对应用户
|
||||
users := make(map[uint]model.User)
|
||||
for _, report := range res {
|
||||
users[report.Share.UserID] = model.User{}
|
||||
}
|
||||
|
||||
userIDs := make([]uint, 0, len(users))
|
||||
for k := range users {
|
||||
userIDs = append(userIDs, k)
|
||||
}
|
||||
|
||||
var userList []model.User
|
||||
model.DB.Where("id in (?)", userIDs).Find(&userList)
|
||||
|
||||
for _, v := range userList {
|
||||
users[v.ID] = v
|
||||
}
|
||||
|
||||
return serializer.Response{Data: map[string]interface{}{
|
||||
"total": total,
|
||||
"items": res,
|
||||
"users": users,
|
||||
"ids": hashIDs,
|
||||
}}
|
||||
}
|
80
service/admin/share.go
Normal file
80
service/admin/share.go
Normal file
@ -0,0 +1,80 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/hashid"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ShareBatchService 分享批量操作服务
|
||||
type ShareBatchService struct {
|
||||
ID []uint `json:"id" binding:"min=1"`
|
||||
}
|
||||
|
||||
// Delete 删除文件
|
||||
func (service *ShareBatchService) Delete(c *gin.Context) serializer.Response {
|
||||
if err := model.DB.Where("id in (?)", service.ID).Delete(&model.Share{}).Error; err != nil {
|
||||
return serializer.DBErr("Failed to delete share record", err)
|
||||
}
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// Shares 列出分享
|
||||
func (service *AdminListService) Shares() serializer.Response {
|
||||
var res []model.Share
|
||||
total := 0
|
||||
|
||||
tx := model.DB.Model(&model.Share{})
|
||||
if service.OrderBy != "" {
|
||||
tx = tx.Order(service.OrderBy)
|
||||
}
|
||||
|
||||
for k, v := range service.Conditions {
|
||||
tx = tx.Where(k+" = ?", v)
|
||||
}
|
||||
|
||||
if len(service.Searches) > 0 {
|
||||
search := ""
|
||||
for k, v := range service.Searches {
|
||||
search += k + " like '%" + v + "%' OR "
|
||||
}
|
||||
search = strings.TrimSuffix(search, " OR ")
|
||||
tx = tx.Where(search)
|
||||
}
|
||||
|
||||
// 计算总数用于分页
|
||||
tx.Count(&total)
|
||||
|
||||
// 查询记录
|
||||
tx.Limit(service.PageSize).Offset((service.Page - 1) * service.PageSize).Find(&res)
|
||||
|
||||
// 查询对应用户,同时计算HashID
|
||||
users := make(map[uint]model.User)
|
||||
hashIDs := make(map[uint]string, len(res))
|
||||
for _, file := range res {
|
||||
users[file.UserID] = model.User{}
|
||||
hashIDs[file.ID] = hashid.HashID(file.ID, hashid.ShareID)
|
||||
}
|
||||
|
||||
userIDs := make([]uint, 0, len(users))
|
||||
for k := range users {
|
||||
userIDs = append(userIDs, k)
|
||||
}
|
||||
|
||||
var userList []model.User
|
||||
model.DB.Where("id in (?)", userIDs).Find(&userList)
|
||||
|
||||
for _, v := range userList {
|
||||
users[v.ID] = v
|
||||
}
|
||||
|
||||
return serializer.Response{Data: map[string]interface{}{
|
||||
"total": total,
|
||||
"items": res,
|
||||
"users": users,
|
||||
"ids": hashIDs,
|
||||
}}
|
||||
}
|
199
service/admin/site.go
Normal file
199
service/admin/site.go
Normal file
@ -0,0 +1,199 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"time"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/email"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/thumb"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/vol"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gob.Register(map[string]interface{}{})
|
||||
gob.Register(map[string]string{})
|
||||
}
|
||||
|
||||
// NoParamService 无需参数的服务
|
||||
type NoParamService struct {
|
||||
}
|
||||
|
||||
// BatchSettingChangeService 设定批量更改服务
|
||||
type BatchSettingChangeService struct {
|
||||
Options []SettingChangeService `json:"options"`
|
||||
}
|
||||
|
||||
// SettingChangeService 设定更改服务
|
||||
type SettingChangeService struct {
|
||||
Key string `json:"key" binding:"required"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// BatchSettingGet 设定批量获取服务
|
||||
type BatchSettingGet struct {
|
||||
Keys []string `json:"keys"`
|
||||
}
|
||||
|
||||
// MailTestService 邮件测试服务
|
||||
type MailTestService struct {
|
||||
Email string `json:"to" binding:"email"`
|
||||
}
|
||||
|
||||
// Send 发送测试邮件
|
||||
func (service *MailTestService) Send() serializer.Response {
|
||||
if err := email.Send(service.Email, "Cloudreve Email delivery test", "This is a test Email, to test Cloudreve Email delivery settings"); err != nil {
|
||||
return serializer.Err(serializer.CodeFailedSendEmail, err.Error(), nil)
|
||||
}
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// Get 获取设定值
|
||||
func (service *BatchSettingGet) Get() serializer.Response {
|
||||
options := model.GetSettingByNames(service.Keys...)
|
||||
return serializer.Response{Data: options}
|
||||
}
|
||||
|
||||
// Change 批量更改站点设定
|
||||
func (service *BatchSettingChangeService) Change() serializer.Response {
|
||||
cacheClean := make([]string, 0, len(service.Options))
|
||||
tx := model.DB.Begin()
|
||||
|
||||
for _, setting := range service.Options {
|
||||
|
||||
if err := tx.Model(&model.Setting{}).Where("name = ?", setting.Key).Update("value", setting.Value).Error; err != nil {
|
||||
cache.Deletes(cacheClean, "setting_")
|
||||
tx.Rollback()
|
||||
return serializer.Err(serializer.CodeUpdateSetting, "Setting "+setting.Key+" failed to update", err)
|
||||
}
|
||||
|
||||
cacheClean = append(cacheClean, setting.Key)
|
||||
}
|
||||
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
return serializer.DBErr("Failed to update setting", err)
|
||||
}
|
||||
|
||||
cache.Deletes(cacheClean, "setting_")
|
||||
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// Summary 获取站点统计概况
|
||||
func (service *NoParamService) Summary() serializer.Response {
|
||||
// 获取版本信息
|
||||
versions := map[string]string{
|
||||
"backend": conf.BackendVersion,
|
||||
"db": conf.RequiredDBVersion,
|
||||
"commit": conf.LastCommit,
|
||||
"is_plus": conf.IsPlus,
|
||||
}
|
||||
|
||||
if res, ok := cache.Get("admin_summary"); ok {
|
||||
resMap := res.(map[string]interface{})
|
||||
resMap["version"] = versions
|
||||
resMap["siteURL"] = model.GetSettingByName("siteURL")
|
||||
return serializer.Response{Data: resMap}
|
||||
}
|
||||
|
||||
// 统计每日概况
|
||||
total := 12
|
||||
files := make([]int, total)
|
||||
users := make([]int, total)
|
||||
shares := make([]int, total)
|
||||
date := make([]string, total)
|
||||
|
||||
toRound := time.Now()
|
||||
timeBase := time.Date(toRound.Year(), toRound.Month(), toRound.Day()+1, 0, 0, 0, 0, toRound.Location())
|
||||
for day := range files {
|
||||
start := timeBase.Add(-time.Duration(total-day) * time.Hour * 24)
|
||||
end := timeBase.Add(-time.Duration(total-day-1) * time.Hour * 24)
|
||||
date[day] = start.Format("1月2日")
|
||||
model.DB.Model(&model.User{}).Where("created_at BETWEEN ? AND ?", start, end).Count(&users[day])
|
||||
model.DB.Model(&model.File{}).Where("created_at BETWEEN ? AND ?", start, end).Count(&files[day])
|
||||
model.DB.Model(&model.Share{}).Where("created_at BETWEEN ? AND ?", start, end).Count(&shares[day])
|
||||
}
|
||||
|
||||
// 统计总数
|
||||
fileTotal := 0
|
||||
userTotal := 0
|
||||
publicShareTotal := 0
|
||||
secretShareTotal := 0
|
||||
model.DB.Model(&model.User{}).Count(&userTotal)
|
||||
model.DB.Model(&model.File{}).Count(&fileTotal)
|
||||
model.DB.Model(&model.Share{}).Where("password = ?", "").Count(&publicShareTotal)
|
||||
model.DB.Model(&model.Share{}).Where("password <> ?", "").Count(&secretShareTotal)
|
||||
|
||||
resp := map[string]interface{}{
|
||||
"date": date,
|
||||
"files": files,
|
||||
"users": users,
|
||||
"shares": shares,
|
||||
"version": versions,
|
||||
"siteURL": model.GetSettingByName("siteURL"),
|
||||
"fileTotal": fileTotal,
|
||||
"userTotal": userTotal,
|
||||
"publicShareTotal": publicShareTotal,
|
||||
"secretShareTotal": secretShareTotal,
|
||||
}
|
||||
|
||||
cache.Set("admin_summary", resp, 86400)
|
||||
return serializer.Response{
|
||||
Data: resp,
|
||||
}
|
||||
}
|
||||
|
||||
// ThumbGeneratorTestService 缩略图生成测试服务
|
||||
type ThumbGeneratorTestService struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Executable string `json:"executable" binding:"required"`
|
||||
}
|
||||
|
||||
// Test 通过获取生成器版本来测试
|
||||
func (s *ThumbGeneratorTestService) Test(c *gin.Context) serializer.Response {
|
||||
version, err := thumb.TestGenerator(c, s.Name, s.Executable)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeParamErr, err.Error(), err)
|
||||
}
|
||||
|
||||
return serializer.Response{
|
||||
Data: version,
|
||||
}
|
||||
}
|
||||
|
||||
// VOL 授权管理服务
|
||||
type VolService struct {
|
||||
}
|
||||
|
||||
// Sync 同步 VOL 授权
|
||||
func (s *VolService) Sync() serializer.Response {
|
||||
volClient := vol.New(vol.ClientSecret)
|
||||
content, signature, err := volClient.Sync()
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, err.Error(), err)
|
||||
}
|
||||
|
||||
subService := &BatchSettingChangeService{
|
||||
Options: []SettingChangeService{
|
||||
{
|
||||
Key: "vol_content",
|
||||
Value: content,
|
||||
},
|
||||
{
|
||||
Key: "vol_signature",
|
||||
Value: signature,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
res := subService.Change()
|
||||
if res.Code != 0 {
|
||||
return res
|
||||
}
|
||||
|
||||
return serializer.Response{Data: content}
|
||||
}
|
159
service/admin/task.go
Normal file
159
service/admin/task.go
Normal file
@ -0,0 +1,159 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/task"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// TaskBatchService 任务批量操作服务
|
||||
type TaskBatchService struct {
|
||||
ID []uint `json:"id" binding:"min=1"`
|
||||
}
|
||||
|
||||
// ImportTaskService 导入任务
|
||||
type ImportTaskService struct {
|
||||
UID uint `json:"uid" binding:"required"`
|
||||
PolicyID uint `json:"policy_id" binding:"required"`
|
||||
Src string `json:"src" binding:"required,min=1,max=65535"`
|
||||
Dst string `json:"dst" binding:"required,min=1,max=65535"`
|
||||
Recursive bool `json:"recursive"`
|
||||
}
|
||||
|
||||
// Create 新建导入任务
|
||||
func (service *ImportTaskService) Create(c *gin.Context, user *model.User) serializer.Response {
|
||||
// 创建任务
|
||||
job, err := task.NewImportTask(service.UID, service.PolicyID, service.Src, service.Dst, service.Recursive)
|
||||
if err != nil {
|
||||
return serializer.DBErr("Failed to create task record.", err)
|
||||
}
|
||||
task.TaskPoll.Submit(job)
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// Delete 删除任务
|
||||
func (service *TaskBatchService) Delete(c *gin.Context) serializer.Response {
|
||||
if err := model.DB.Where("id in (?)", service.ID).Delete(&model.Download{}).Error; err != nil {
|
||||
return serializer.DBErr("Failed to delete task records", err)
|
||||
}
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// DeleteGeneral 删除常规任务
|
||||
func (service *TaskBatchService) DeleteGeneral(c *gin.Context) serializer.Response {
|
||||
if err := model.DB.Where("id in (?)", service.ID).Delete(&model.Task{}).Error; err != nil {
|
||||
return serializer.DBErr("Failed to delete task records", err)
|
||||
}
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// Tasks 列出常规任务
|
||||
func (service *AdminListService) Tasks() serializer.Response {
|
||||
var res []model.Task
|
||||
total := 0
|
||||
|
||||
tx := model.DB.Model(&model.Task{})
|
||||
if service.OrderBy != "" {
|
||||
tx = tx.Order(service.OrderBy)
|
||||
}
|
||||
|
||||
for k, v := range service.Conditions {
|
||||
tx = tx.Where(k+" = ?", v)
|
||||
}
|
||||
|
||||
if len(service.Searches) > 0 {
|
||||
search := ""
|
||||
for k, v := range service.Searches {
|
||||
search += k + " like '%" + v + "%' OR "
|
||||
}
|
||||
search = strings.TrimSuffix(search, " OR ")
|
||||
tx = tx.Where(search)
|
||||
}
|
||||
|
||||
// 计算总数用于分页
|
||||
tx.Count(&total)
|
||||
|
||||
// 查询记录
|
||||
tx.Limit(service.PageSize).Offset((service.Page - 1) * service.PageSize).Find(&res)
|
||||
|
||||
// 查询对应用户,同时计算HashID
|
||||
users := make(map[uint]model.User)
|
||||
for _, file := range res {
|
||||
users[file.UserID] = model.User{}
|
||||
}
|
||||
|
||||
userIDs := make([]uint, 0, len(users))
|
||||
for k := range users {
|
||||
userIDs = append(userIDs, k)
|
||||
}
|
||||
|
||||
var userList []model.User
|
||||
model.DB.Where("id in (?)", userIDs).Find(&userList)
|
||||
|
||||
for _, v := range userList {
|
||||
users[v.ID] = v
|
||||
}
|
||||
|
||||
return serializer.Response{Data: map[string]interface{}{
|
||||
"total": total,
|
||||
"items": res,
|
||||
"users": users,
|
||||
}}
|
||||
}
|
||||
|
||||
// Downloads 列出离线下载任务
|
||||
func (service *AdminListService) Downloads() serializer.Response {
|
||||
var res []model.Download
|
||||
total := 0
|
||||
|
||||
tx := model.DB.Model(&model.Download{})
|
||||
if service.OrderBy != "" {
|
||||
tx = tx.Order(service.OrderBy)
|
||||
}
|
||||
|
||||
for k, v := range service.Conditions {
|
||||
tx = tx.Where(k+" = ?", v)
|
||||
}
|
||||
|
||||
if len(service.Searches) > 0 {
|
||||
search := ""
|
||||
for k, v := range service.Searches {
|
||||
search += k + " like '%" + v + "%' OR "
|
||||
}
|
||||
search = strings.TrimSuffix(search, " OR ")
|
||||
tx = tx.Where(search)
|
||||
}
|
||||
|
||||
// 计算总数用于分页
|
||||
tx.Count(&total)
|
||||
|
||||
// 查询记录
|
||||
tx.Limit(service.PageSize).Offset((service.Page - 1) * service.PageSize).Find(&res)
|
||||
|
||||
// 查询对应用户,同时计算HashID
|
||||
users := make(map[uint]model.User)
|
||||
for _, file := range res {
|
||||
users[file.UserID] = model.User{}
|
||||
}
|
||||
|
||||
userIDs := make([]uint, 0, len(users))
|
||||
for k := range users {
|
||||
userIDs = append(userIDs, k)
|
||||
}
|
||||
|
||||
var userList []model.User
|
||||
model.DB.Where("id in (?)", userIDs).Find(&userList)
|
||||
|
||||
for _, v := range userList {
|
||||
users[v.ID] = v
|
||||
}
|
||||
|
||||
return serializer.Response{Data: map[string]interface{}{
|
||||
"total": total,
|
||||
"items": res,
|
||||
"users": users,
|
||||
}}
|
||||
}
|
179
service/admin/user.go
Normal file
179
service/admin/user.go
Normal file
@ -0,0 +1,179 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
||||
)
|
||||
|
||||
// AddUserService 用户添加服务
|
||||
type AddUserService struct {
|
||||
User model.User `json:"User" binding:"required"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// UserService 用户ID服务
|
||||
type UserService struct {
|
||||
ID uint `uri:"id" json:"id" binding:"required"`
|
||||
}
|
||||
|
||||
// UserBatchService 用户批量操作服务
|
||||
type UserBatchService struct {
|
||||
ID []uint `json:"id" binding:"min=1"`
|
||||
}
|
||||
|
||||
// Ban 封禁/解封用户
|
||||
func (service *UserService) Ban() serializer.Response {
|
||||
user, err := model.GetUserByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeUserNotFound, "", err)
|
||||
}
|
||||
|
||||
if user.ID == 1 {
|
||||
return serializer.Err(serializer.CodeInvalidActionOnDefaultUser, "", err)
|
||||
}
|
||||
|
||||
if user.Status == model.Active {
|
||||
user.SetStatus(model.Baned)
|
||||
} else {
|
||||
user.SetStatus(model.Active)
|
||||
}
|
||||
|
||||
return serializer.Response{Data: user.Status}
|
||||
}
|
||||
|
||||
// Delete 删除用户
|
||||
func (service *UserBatchService) Delete() serializer.Response {
|
||||
for _, uid := range service.ID {
|
||||
user, err := model.GetUserByID(uid)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeUserNotFound, "", err)
|
||||
}
|
||||
|
||||
// 不能删除初始用户
|
||||
if uid == 1 {
|
||||
return serializer.Err(serializer.CodeInvalidActionOnDefaultUser, "", err)
|
||||
}
|
||||
|
||||
// 删除与此用户相关的所有资源
|
||||
|
||||
fs, err := filesystem.NewFileSystem(&user)
|
||||
// 删除所有文件
|
||||
root, err := fs.User.Root()
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "User's root folder not exist", err)
|
||||
}
|
||||
fs.Delete(context.Background(), []uint{root.ID}, []uint{}, false, false)
|
||||
|
||||
// 删除相关任务
|
||||
model.DB.Where("user_id = ?", uid).Delete(&model.Download{})
|
||||
model.DB.Where("user_id = ?", uid).Delete(&model.Task{})
|
||||
|
||||
// 删除订单记录
|
||||
model.DB.Where("user_id = ?", uid).Delete(&model.Order{})
|
||||
|
||||
// 删除容量包
|
||||
model.DB.Where("user_id = ?", uid).Delete(&model.StoragePack{})
|
||||
|
||||
// 删除标签
|
||||
model.DB.Where("user_id = ?", uid).Delete(&model.Tag{})
|
||||
|
||||
// 删除WebDAV账号
|
||||
model.DB.Where("user_id = ?", uid).Delete(&model.Webdav{})
|
||||
|
||||
// 删除此用户
|
||||
model.DB.Unscoped().Delete(user)
|
||||
|
||||
}
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// Get 获取用户详情
|
||||
func (service *UserService) Get() serializer.Response {
|
||||
group, err := model.GetUserByID(service.ID)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeUserNotFound, "", err)
|
||||
}
|
||||
|
||||
return serializer.Response{Data: group}
|
||||
}
|
||||
|
||||
// Add 添加用户
|
||||
func (service *AddUserService) Add() serializer.Response {
|
||||
if service.User.ID > 0 {
|
||||
|
||||
user, _ := model.GetUserByID(service.User.ID)
|
||||
if service.Password != "" {
|
||||
user.SetPassword(service.Password)
|
||||
}
|
||||
|
||||
// 只更新必要字段
|
||||
user.Nick = service.User.Nick
|
||||
user.Email = service.User.Email
|
||||
user.GroupID = service.User.GroupID
|
||||
user.Status = service.User.Status
|
||||
user.Score = service.User.Score
|
||||
user.TwoFactor = service.User.TwoFactor
|
||||
|
||||
// 检查愚蠢操作
|
||||
if user.ID == 1 {
|
||||
if user.GroupID != 1 {
|
||||
return serializer.Err(serializer.CodeChangeGroupForDefaultUser, "", nil)
|
||||
}
|
||||
if user.Status != model.Active {
|
||||
return serializer.Err(serializer.CodeInvalidActionOnDefaultUser, "", nil)
|
||||
}
|
||||
}
|
||||
|
||||
if err := model.DB.Save(&user).Error; err != nil {
|
||||
return serializer.DBErr("Failed to save user record", err)
|
||||
}
|
||||
} else {
|
||||
service.User.SetPassword(service.Password)
|
||||
if err := model.DB.Create(&service.User).Error; err != nil {
|
||||
return serializer.DBErr("Failed to create user record", err)
|
||||
}
|
||||
}
|
||||
|
||||
return serializer.Response{Data: service.User.ID}
|
||||
}
|
||||
|
||||
// Users 列出用户
|
||||
func (service *AdminListService) Users() serializer.Response {
|
||||
var res []model.User
|
||||
total := 0
|
||||
|
||||
tx := model.DB.Model(&model.User{})
|
||||
if service.OrderBy != "" {
|
||||
tx = tx.Order(service.OrderBy)
|
||||
}
|
||||
|
||||
for k, v := range service.Conditions {
|
||||
tx = tx.Where(k+" = ?", v)
|
||||
}
|
||||
|
||||
if len(service.Searches) > 0 {
|
||||
search := ""
|
||||
for k, v := range service.Searches {
|
||||
search += (k + " like '%" + v + "%' OR ")
|
||||
}
|
||||
search = strings.TrimSuffix(search, " OR ")
|
||||
tx = tx.Where(search)
|
||||
}
|
||||
|
||||
// 计算总数用于分页
|
||||
tx.Count(&total)
|
||||
|
||||
// 查询记录
|
||||
tx.Set("gorm:auto_preload", true).Limit(service.PageSize).Offset((service.Page - 1) * service.PageSize).Find(&res)
|
||||
|
||||
// 补齐缺失用户组
|
||||
|
||||
return serializer.Response{Data: map[string]interface{}{
|
||||
"total": total,
|
||||
"items": res,
|
||||
}}
|
||||
}
|
98
service/admin/vas.go
Normal file
98
service/admin/vas.go
Normal file
@ -0,0 +1,98 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
// GenerateRedeemsService 兑换码生成服务
|
||||
type GenerateRedeemsService struct {
|
||||
Num int `json:"num" binding:"required,min=1,max=100"`
|
||||
ID int64 `json:"id"`
|
||||
Time int `json:"time" binding:"required,min=1"`
|
||||
Type int `json:"type" binding:"min=0,max=2"`
|
||||
}
|
||||
|
||||
// SingleIDService 单ID服务
|
||||
type SingleIDService struct {
|
||||
ID uint `uri:"id" binding:"required"`
|
||||
}
|
||||
|
||||
// DeleteRedeem 删除兑换码
|
||||
func (service *SingleIDService) DeleteRedeem() serializer.Response {
|
||||
if err := model.DB.Where("id = ?", service.ID).Delete(&model.Redeem{}).Error; err != nil {
|
||||
return serializer.DBErr("Failed to delete gift code record.", err)
|
||||
}
|
||||
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// Generate 生成兑换码
|
||||
func (service *GenerateRedeemsService) Generate() serializer.Response {
|
||||
res := make([]string, service.Num)
|
||||
redeem := model.Redeem{}
|
||||
|
||||
// 开始事务
|
||||
tx := model.DB.Begin()
|
||||
if err := tx.Error; err != nil {
|
||||
return serializer.DBErr("Cannot start transaction", err)
|
||||
}
|
||||
|
||||
// 创建每个兑换码
|
||||
for i := 0; i < service.Num; i++ {
|
||||
redeem.Model.ID = 0
|
||||
redeem.Num = service.Time
|
||||
redeem.Type = service.Type
|
||||
redeem.ProductID = service.ID
|
||||
redeem.Used = false
|
||||
|
||||
// 生成唯一兑换码
|
||||
u2, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return serializer.Err(serializer.CodeInternalSetting, "Failed to generate UUID", err)
|
||||
}
|
||||
|
||||
redeem.Code = u2.String()
|
||||
if err := tx.Create(&redeem).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return serializer.DBErr("Failed to insert gift code record", err)
|
||||
}
|
||||
|
||||
res[i] = redeem.Code
|
||||
}
|
||||
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
return serializer.DBErr("Failed to insert gift code record", err)
|
||||
}
|
||||
|
||||
return serializer.Response{Data: res}
|
||||
|
||||
}
|
||||
|
||||
// Redeems 列出激活码
|
||||
func (service *AdminListService) Redeems() serializer.Response {
|
||||
var res []model.Redeem
|
||||
total := 0
|
||||
|
||||
tx := model.DB.Model(&model.Redeem{})
|
||||
if service.OrderBy != "" {
|
||||
tx = tx.Order(service.OrderBy)
|
||||
}
|
||||
|
||||
for k, v := range service.Conditions {
|
||||
tx = tx.Where("? = ?", k, v)
|
||||
}
|
||||
|
||||
// 计算总数用于分页
|
||||
tx.Count(&total)
|
||||
|
||||
// 查询记录
|
||||
tx.Limit(service.PageSize).Offset((service.Page - 1) * service.PageSize).Find(&res)
|
||||
|
||||
return serializer.Response{Data: map[string]interface{}{
|
||||
"total": total,
|
||||
"items": res,
|
||||
}}
|
||||
}
|
Reference in New Issue
Block a user