mirror of https://github.com/Xhofe/alist
feat: add tag backup and fix bugs (#9265)
* feat(label): enhance label file binding and router setup (feat/add-tag-backup) - Add `GetLabelsByFileNamesPublic` to retrieve labels using file names. - Refactor router setup for label and file binding routes. - Improve `toObjsResp` for efficient label retrieval by file names. - Comment out unnecessary user ID parameter in `toObjsResp`. * feat(label): enhance label file binding and router setup - Add `GetLabelsByFileNamesPublic` for label retrieval by file names. - Refactor router setup for label and file binding routes. - Improve `toObjsResp` for efficient label retrieval by file names. - Comment out unnecessary user ID parameter in `toObjsResp`. * refactor(db): comment out debug print in GetLabelIds (#feat/add-tag-backup) - Comment out debug print statement in GetLabelIds to clean up logs. - Enhance code readability by removing unnecessary debug output. * feat(label-file-binding): add batch creation and improve label ID handling - Introduced `CreateLabelFileBinDingBatch` API for batch label binding. - Added `collectLabelIDs` helper function to handle label ID parsing. - Enhanced label ID handling to support varied delimiters and input formats. - Refactored `CreateLabelFileBinDing` logic for improved code readability. - Updated router to include `POST /label_file_binding/create_batch`.fix/ci-build-error
parent
6b2d81eede
commit
aea3ba1499
|
@ -12,7 +12,7 @@ var db *gorm.DB
|
||||||
|
|
||||||
func Init(d *gorm.DB) {
|
func Init(d *gorm.DB) {
|
||||||
db = d
|
db = d
|
||||||
err := AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem), new(model.SearchNode), new(model.TaskItem), new(model.SSHPublicKey), new(model.Role), new(model.Label), new(model.LabelFileBinDing), new(model.ObjFile))
|
err := AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem), new(model.SearchNode), new(model.TaskItem), new(model.SSHPublicKey), new(model.Role), new(model.Label), new(model.LabelFileBinding), new(model.ObjFile))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed migrate database: %s", err.Error())
|
log.Fatalf("failed migrate database: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetLabelIds Get all label_ids from database order by file_name
|
// GetLabelIds Get all label_ids from database order by file_name
|
||||||
func GetLabelIds(userId uint, fileName string) ([]uint, error) {
|
func GetLabelIds(userId uint, fileName string) ([]uint, error) {
|
||||||
labelFileBinDingDB := db.Model(&model.LabelFileBinDing{})
|
//fmt.Printf(">>> [GetLabelIds] userId: %d, fileName: %s\n", userId, fileName)
|
||||||
|
labelFileBinDingDB := db.Model(&model.LabelFileBinding{})
|
||||||
var labelIds []uint
|
var labelIds []uint
|
||||||
if err := labelFileBinDingDB.Where("file_name = ?", fileName).Where("user_id = ?", userId).Pluck("label_id", &labelIds).Error; err != nil {
|
if err := labelFileBinDingDB.Where("file_name = ?", fileName).Where("user_id = ?", userId).Pluck("label_id", &labelIds).Error; err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
|
@ -18,7 +21,7 @@ func GetLabelIds(userId uint, fileName string) ([]uint, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateLabelFileBinDing(fileName string, labelId, userId uint) error {
|
func CreateLabelFileBinDing(fileName string, labelId, userId uint) error {
|
||||||
var labelFileBinDing model.LabelFileBinDing
|
var labelFileBinDing model.LabelFileBinding
|
||||||
labelFileBinDing.UserId = userId
|
labelFileBinDing.UserId = userId
|
||||||
labelFileBinDing.LabelId = labelId
|
labelFileBinDing.LabelId = labelId
|
||||||
labelFileBinDing.FileName = fileName
|
labelFileBinDing.FileName = fileName
|
||||||
|
@ -32,7 +35,7 @@ func CreateLabelFileBinDing(fileName string, labelId, userId uint) error {
|
||||||
|
|
||||||
// GetLabelFileBinDingByLabelIdExists Get Label by label_id, used to del label usually
|
// GetLabelFileBinDingByLabelIdExists Get Label by label_id, used to del label usually
|
||||||
func GetLabelFileBinDingByLabelIdExists(labelId, userId uint) bool {
|
func GetLabelFileBinDingByLabelIdExists(labelId, userId uint) bool {
|
||||||
var labelFileBinDing model.LabelFileBinDing
|
var labelFileBinDing model.LabelFileBinding
|
||||||
result := db.Where("label_id = ?", labelId).Where("user_id = ?", userId).First(&labelFileBinDing)
|
result := db.Where("label_id = ?", labelId).Where("user_id = ?", userId).First(&labelFileBinDing)
|
||||||
exists := !errors.Is(result.Error, gorm.ErrRecordNotFound)
|
exists := !errors.Is(result.Error, gorm.ErrRecordNotFound)
|
||||||
return exists
|
return exists
|
||||||
|
@ -40,17 +43,150 @@ func GetLabelFileBinDingByLabelIdExists(labelId, userId uint) bool {
|
||||||
|
|
||||||
// DelLabelFileBinDingByFileName used to del usually
|
// DelLabelFileBinDingByFileName used to del usually
|
||||||
func DelLabelFileBinDingByFileName(userId uint, fileName string) error {
|
func DelLabelFileBinDingByFileName(userId uint, fileName string) error {
|
||||||
return errors.WithStack(db.Where("file_name = ?", fileName).Where("user_id = ?", userId).Delete(model.LabelFileBinDing{}).Error)
|
return errors.WithStack(db.Where("file_name = ?", fileName).Where("user_id = ?", userId).Delete(model.LabelFileBinding{}).Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DelLabelFileBinDingById used to del usually
|
// DelLabelFileBinDingById used to del usually
|
||||||
func DelLabelFileBinDingById(labelId, userId uint, fileName string) error {
|
func DelLabelFileBinDingById(labelId, userId uint, fileName string) error {
|
||||||
return errors.WithStack(db.Where("label_id = ?", labelId).Where("file_name = ?", fileName).Where("user_id = ?", userId).Delete(model.LabelFileBinDing{}).Error)
|
return errors.WithStack(db.Where("label_id = ?", labelId).Where("file_name = ?", fileName).Where("user_id = ?", userId).Delete(model.LabelFileBinding{}).Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetLabelFileBinDingByLabelId(labelIds []uint, userId uint) (result []model.LabelFileBinDing, err error) {
|
func GetLabelFileBinDingByLabelId(labelIds []uint, userId uint) (result []model.LabelFileBinding, err error) {
|
||||||
if err := db.Where("label_id in (?)", labelIds).Where("user_id = ?", userId).Find(&result).Error; err != nil {
|
if err := db.Where("label_id in (?)", labelIds).Where("user_id = ?", userId).Find(&result).Error; err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetLabelBindingsByFileNamesPublic(fileNames []string) (map[string][]uint, error) {
|
||||||
|
var binds []model.LabelFileBinding
|
||||||
|
if err := db.Where("file_name IN ?", fileNames).Find(&binds).Error; err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
out := make(map[string][]uint, len(fileNames))
|
||||||
|
seen := make(map[string]struct{}, len(binds))
|
||||||
|
for _, b := range binds {
|
||||||
|
key := fmt.Sprintf("%s-%d", b.FileName, b.LabelId)
|
||||||
|
if _, ok := seen[key]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[key] = struct{}{}
|
||||||
|
out[b.FileName] = append(out[b.FileName], b.LabelId)
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLabelsByFileNamesPublic(fileNames []string) (map[string][]model.Label, error) {
|
||||||
|
bindMap, err := GetLabelBindingsByFileNamesPublic(fileNames)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
idSet := make(map[uint]struct{})
|
||||||
|
for _, ids := range bindMap {
|
||||||
|
for _, id := range ids {
|
||||||
|
idSet[id] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(idSet) == 0 {
|
||||||
|
return make(map[string][]model.Label, 0), nil
|
||||||
|
}
|
||||||
|
allIDs := make([]uint, 0, len(idSet))
|
||||||
|
for id := range idSet {
|
||||||
|
allIDs = append(allIDs, id)
|
||||||
|
}
|
||||||
|
labels, err := GetLabelByIds(allIDs) // 你已有的函数
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
labelByID := make(map[uint]model.Label, len(labels))
|
||||||
|
for _, l := range labels {
|
||||||
|
labelByID[l.ID] = l
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make(map[string][]model.Label, len(bindMap))
|
||||||
|
for fname, ids := range bindMap {
|
||||||
|
for _, id := range ids {
|
||||||
|
if lab, ok := labelByID[id]; ok {
|
||||||
|
out[fname] = append(out[fname], lab)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListLabelFileBinDing(userId uint, labelIDs []uint, fileName string, page, pageSize int) ([]model.LabelFileBinding, int64, error) {
|
||||||
|
q := db.Model(&model.LabelFileBinding{}).Where("user_id = ?", userId)
|
||||||
|
|
||||||
|
if len(labelIDs) > 0 {
|
||||||
|
q = q.Where("label_id IN ?", labelIDs)
|
||||||
|
}
|
||||||
|
if fileName != "" {
|
||||||
|
q = q.Where("file_name LIKE ?", "%"+fileName+"%")
|
||||||
|
}
|
||||||
|
|
||||||
|
var total int64
|
||||||
|
if err := q.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var rows []model.LabelFileBinding
|
||||||
|
if err := q.
|
||||||
|
Order("id DESC").
|
||||||
|
Offset((page - 1) * pageSize).
|
||||||
|
Limit(pageSize).
|
||||||
|
Find(&rows).Error; err != nil {
|
||||||
|
return nil, 0, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return rows, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RestoreLabelFileBindings(bindings []model.LabelFileBinding, keepIDs bool, override bool) error {
|
||||||
|
if len(bindings) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tx := db.Begin()
|
||||||
|
|
||||||
|
if override {
|
||||||
|
type key struct {
|
||||||
|
uid uint
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
toDel := make(map[key]struct{}, len(bindings))
|
||||||
|
for i := range bindings {
|
||||||
|
k := key{uid: bindings[i].UserId, name: bindings[i].FileName}
|
||||||
|
toDel[k] = struct{}{}
|
||||||
|
}
|
||||||
|
for k := range toDel {
|
||||||
|
if err := tx.Where("user_id = ? AND file_name = ?", k.uid, k.name).
|
||||||
|
Delete(&model.LabelFileBinding{}).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range bindings {
|
||||||
|
b := bindings[i]
|
||||||
|
if !keepIDs {
|
||||||
|
b.ID = 0
|
||||||
|
}
|
||||||
|
if b.CreateTime.IsZero() {
|
||||||
|
b.CreateTime = time.Now()
|
||||||
|
}
|
||||||
|
if override {
|
||||||
|
if err := tx.Create(&b).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&b).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.WithStack(tx.Commit().Error)
|
||||||
|
}
|
||||||
|
|
|
@ -3,5 +3,5 @@ package errs
|
||||||
import "errors"
|
import "errors"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrChangeDefaultRole = errors.New("cannot modify admin or guest role")
|
ErrChangeDefaultRole = errors.New("cannot modify admin role")
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,7 +2,7 @@ package model
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type LabelFileBinDing struct {
|
type LabelFileBinding struct {
|
||||||
ID uint `json:"id" gorm:"primaryKey"` // unique key
|
ID uint `json:"id" gorm:"primaryKey"` // unique key
|
||||||
UserId uint `json:"user_id"` // use to user_id
|
UserId uint `json:"user_id"` // use to user_id
|
||||||
LabelId uint `json:"label_id"` // use to label_id
|
LabelId uint `json:"label_id"` // use to label_id
|
||||||
|
|
|
@ -23,6 +23,7 @@ type CreateLabelFileBinDingReq struct {
|
||||||
Type int `json:"type"`
|
Type int `json:"type"`
|
||||||
HashInfoStr string `json:"hashinfo"`
|
HashInfoStr string `json:"hashinfo"`
|
||||||
LabelIds string `json:"label_ids"`
|
LabelIds string `json:"label_ids"`
|
||||||
|
LabelIDs []uint64 `json:"labelIdList"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ObjLabelResp struct {
|
type ObjLabelResp struct {
|
||||||
|
@ -54,23 +55,29 @@ func GetLabelByFileName(userId uint, fileName string) ([]model.Label, error) {
|
||||||
return labels, nil
|
return labels, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetLabelsByFileNamesPublic(fileNames []string) (map[string][]model.Label, error) {
|
||||||
|
return db.GetLabelsByFileNamesPublic(fileNames)
|
||||||
|
}
|
||||||
|
|
||||||
func CreateLabelFileBinDing(req CreateLabelFileBinDingReq, userId uint) error {
|
func CreateLabelFileBinDing(req CreateLabelFileBinDingReq, userId uint) error {
|
||||||
if err := db.DelLabelFileBinDingByFileName(userId, req.Name); err != nil {
|
if err := db.DelLabelFileBinDingByFileName(userId, req.Name); err != nil {
|
||||||
return errors.WithMessage(err, "failed del label_file_bin_ding in database")
|
return errors.WithMessage(err, "failed del label_file_bin_ding in database")
|
||||||
}
|
}
|
||||||
if req.LabelIds == "" {
|
|
||||||
|
ids, err := collectLabelIDs(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(ids) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
labelMap := strings.Split(req.LabelIds, ",")
|
|
||||||
for _, value := range labelMap {
|
for _, id := range ids {
|
||||||
labelId, err := strconv.ParseUint(value, 10, 64)
|
if err = db.CreateLabelFileBinDing(req.Name, uint(id), userId); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid label ID '%s': %v", value, err)
|
|
||||||
}
|
|
||||||
if err = db.CreateLabelFileBinDing(req.Name, uint(labelId), userId); err != nil {
|
|
||||||
return errors.WithMessage(err, "failed labels in database")
|
return errors.WithMessage(err, "failed labels in database")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !db.GetFileByNameExists(req.Name) {
|
if !db.GetFileByNameExists(req.Name) {
|
||||||
objFile := model.ObjFile{
|
objFile := model.ObjFile{
|
||||||
Id: req.Id,
|
Id: req.Id,
|
||||||
|
@ -86,8 +93,7 @@ func CreateLabelFileBinDing(req CreateLabelFileBinDingReq, userId uint) error {
|
||||||
Type: req.Type,
|
Type: req.Type,
|
||||||
HashInfoStr: req.HashInfoStr,
|
HashInfoStr: req.HashInfoStr,
|
||||||
}
|
}
|
||||||
err := db.CreateObjFile(objFile)
|
if err := db.CreateObjFile(objFile); err != nil {
|
||||||
if err != nil {
|
|
||||||
return errors.WithMessage(err, "failed file in database")
|
return errors.WithMessage(err, "failed file in database")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,7 +103,7 @@ func CreateLabelFileBinDing(req CreateLabelFileBinDingReq, userId uint) error {
|
||||||
func GetFileByLabel(userId uint, labelId string) (result []ObjLabelResp, err error) {
|
func GetFileByLabel(userId uint, labelId string) (result []ObjLabelResp, err error) {
|
||||||
labelMap := strings.Split(labelId, ",")
|
labelMap := strings.Split(labelId, ",")
|
||||||
var labelIds []uint
|
var labelIds []uint
|
||||||
var labelsFile []model.LabelFileBinDing
|
var labelsFile []model.LabelFileBinding
|
||||||
var labels []model.Label
|
var labels []model.Label
|
||||||
var labelsFileMap = make(map[string][]model.Label)
|
var labelsFileMap = make(map[string][]model.Label)
|
||||||
var labelsMap = make(map[uint]model.Label)
|
var labelsMap = make(map[uint]model.Label)
|
||||||
|
@ -157,3 +163,33 @@ func StringSliceToUintSlice(strSlice []string) ([]uint, error) {
|
||||||
}
|
}
|
||||||
return uintSlice, nil
|
return uintSlice, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RestoreLabelFileBindings(bindings []model.LabelFileBinding, keepIDs bool, override bool) error {
|
||||||
|
return db.RestoreLabelFileBindings(bindings, keepIDs, override)
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectLabelIDs(req CreateLabelFileBinDingReq) ([]uint64, error) {
|
||||||
|
if len(req.LabelIDs) > 0 {
|
||||||
|
return req.LabelIDs, nil
|
||||||
|
}
|
||||||
|
s := strings.TrimSpace(req.LabelIds)
|
||||||
|
if s == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
replacer := strings.NewReplacer(",", ",", "、", ",", ";", ",", ";", ",")
|
||||||
|
s = replacer.Replace(s)
|
||||||
|
parts := strings.Split(s, ",")
|
||||||
|
ids := make([]uint64, 0, len(parts))
|
||||||
|
for _, p := range parts {
|
||||||
|
p = strings.TrimSpace(p)
|
||||||
|
if p == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
id, err := strconv.ParseUint(p, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid label ID '%s': %v", p, err)
|
||||||
|
}
|
||||||
|
ids = append(ids, id)
|
||||||
|
}
|
||||||
|
return ids, nil
|
||||||
|
}
|
||||||
|
|
|
@ -114,7 +114,7 @@ func FsList(c *gin.Context) {
|
||||||
provider = storage.GetStorage().Driver
|
provider = storage.GetStorage().Driver
|
||||||
}
|
}
|
||||||
common.SuccessResp(c, FsListResp{
|
common.SuccessResp(c, FsListResp{
|
||||||
Content: toObjsResp(objs, reqPath, isEncrypt(meta, reqPath), user.ID),
|
Content: toObjsResp(objs, reqPath, isEncrypt(meta, reqPath)),
|
||||||
Total: int64(total),
|
Total: int64(total),
|
||||||
Readme: getReadme(meta, reqPath),
|
Readme: getReadme(meta, reqPath),
|
||||||
Header: getHeader(meta, reqPath),
|
Header: getHeader(meta, reqPath),
|
||||||
|
@ -224,12 +224,22 @@ func pagination(objs []model.Obj, req *model.PageReq) (int, []model.Obj) {
|
||||||
return total, objs[start:end]
|
return total, objs[start:end]
|
||||||
}
|
}
|
||||||
|
|
||||||
func toObjsResp(objs []model.Obj, parent string, encrypt bool, userId uint) []ObjLabelResp {
|
func toObjsResp(objs []model.Obj, parent string, encrypt bool) []ObjLabelResp {
|
||||||
var resp []ObjLabelResp
|
var resp []ObjLabelResp
|
||||||
|
|
||||||
|
names := make([]string, 0, len(objs))
|
||||||
|
for _, obj := range objs {
|
||||||
|
if !obj.IsDir() {
|
||||||
|
names = append(names, obj.GetName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
labelsByName, _ := op.GetLabelsByFileNamesPublic(names)
|
||||||
|
|
||||||
for _, obj := range objs {
|
for _, obj := range objs {
|
||||||
var labels []model.Label
|
var labels []model.Label
|
||||||
if obj.IsDir() == false {
|
if !obj.IsDir() {
|
||||||
labels, _ = op.GetLabelByFileName(userId, obj.GetName())
|
labels = labelsByName[obj.GetName()]
|
||||||
}
|
}
|
||||||
thumb, _ := model.GetThumb(obj)
|
thumb, _ := model.GetThumb(obj)
|
||||||
resp = append(resp, ObjLabelResp{
|
resp = append(resp, ObjLabelResp{
|
||||||
|
@ -369,7 +379,7 @@ func FsGet(c *gin.Context) {
|
||||||
Readme: getReadme(meta, reqPath),
|
Readme: getReadme(meta, reqPath),
|
||||||
Header: getHeader(meta, reqPath),
|
Header: getHeader(meta, reqPath),
|
||||||
Provider: provider,
|
Provider: provider,
|
||||||
Related: toObjsResp(related, parentPath, isEncrypt(parentMeta, parentPath), user.ID),
|
Related: toObjsResp(related, parentPath, isEncrypt(parentMeta, parentPath)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,9 @@ import (
|
||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
"github.com/alist-org/alist/v3/server/common"
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DelLabelFileBinDingReq struct {
|
type DelLabelFileBinDingReq struct {
|
||||||
|
@ -16,18 +18,36 @@ type DelLabelFileBinDingReq struct {
|
||||||
LabelId string `json:"label_id"`
|
LabelId string `json:"label_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type pageResp[T any] struct {
|
||||||
|
Content []T `json:"content"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type restoreLabelBindingsReq struct {
|
||||||
|
KeepIDs bool `json:"keep_ids"`
|
||||||
|
Override bool `json:"override"`
|
||||||
|
Bindings []model.LabelFileBinding `json:"bindings"`
|
||||||
|
}
|
||||||
|
|
||||||
func GetLabelByFileName(c *gin.Context) {
|
func GetLabelByFileName(c *gin.Context) {
|
||||||
fileName := c.Query("file_name")
|
fileName := c.Query("file_name")
|
||||||
if fileName == "" {
|
if fileName == "" {
|
||||||
common.ErrorResp(c, errors.New("file_name must not empty"), 400)
|
common.ErrorResp(c, errors.New("file_name must not empty"), 400)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
decodedFileName, err := url.QueryUnescape(fileName)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, errors.New("invalid file_name"), 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(">>> 原始 fileName:", fileName)
|
||||||
|
fmt.Println(">>> 解码后 fileName:", decodedFileName)
|
||||||
userObj, ok := c.Value("user").(*model.User)
|
userObj, ok := c.Value("user").(*model.User)
|
||||||
if !ok {
|
if !ok {
|
||||||
common.ErrorStrResp(c, "user invalid", 401)
|
common.ErrorStrResp(c, "user invalid", 401)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
labels, err := op.GetLabelByFileName(userObj.ID, fileName)
|
labels, err := op.GetLabelByFileName(userObj.ID, decodedFileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500, true)
|
common.ErrorResp(c, err, 500, true)
|
||||||
return
|
return
|
||||||
|
@ -101,3 +121,130 @@ func GetFileByLabel(c *gin.Context) {
|
||||||
}
|
}
|
||||||
common.SuccessResp(c, fileList)
|
common.SuccessResp(c, fileList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ListLabelFileBinding(c *gin.Context) {
|
||||||
|
userObj, ok := c.Value("user").(*model.User)
|
||||||
|
if !ok {
|
||||||
|
common.ErrorStrResp(c, "user invalid", 401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pageStr := c.DefaultQuery("page", "1")
|
||||||
|
sizeStr := c.DefaultQuery("page_size", "50")
|
||||||
|
page, err := strconv.Atoi(pageStr)
|
||||||
|
if err != nil || page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
pageSize, err := strconv.Atoi(sizeStr)
|
||||||
|
if err != nil || pageSize <= 0 || pageSize > 200 {
|
||||||
|
pageSize = 50
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := c.Query("file_name")
|
||||||
|
labelIDStr := c.Query("label_id")
|
||||||
|
var labelIDs []uint
|
||||||
|
if labelIDStr != "" {
|
||||||
|
parts := strings.Split(labelIDStr, ",")
|
||||||
|
for _, p := range parts {
|
||||||
|
if p == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
id64, err := strconv.ParseUint(strings.TrimSpace(p), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, fmt.Errorf("invalid label_id '%s': %v", p, err), 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
labelIDs = append(labelIDs, uint(id64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list, total, err := db.ListLabelFileBinDing(userObj.ID, labelIDs, fileName, page, pageSize)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c, pageResp[model.LabelFileBinding]{
|
||||||
|
Content: list,
|
||||||
|
Total: total,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func RestoreLabelFileBinding(c *gin.Context) {
|
||||||
|
var req restoreLabelBindingsReq
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(req.Bindings) == 0 {
|
||||||
|
common.ErrorStrResp(c, "empty bindings", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if u, ok := c.Value("user").(*model.User); ok {
|
||||||
|
for i := range req.Bindings {
|
||||||
|
if req.Bindings[i].UserId == 0 {
|
||||||
|
req.Bindings[i].UserId = u.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range req.Bindings {
|
||||||
|
b := req.Bindings[i]
|
||||||
|
if b.UserId == 0 || b.LabelId == 0 || strings.TrimSpace(b.FileName) == "" {
|
||||||
|
common.ErrorStrResp(c, "invalid binding: user_id/label_id/file_name required", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := op.RestoreLabelFileBindings(req.Bindings, req.KeepIDs, req.Override); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c, gin.H{
|
||||||
|
"msg": fmt.Sprintf("restored %d rows", len(req.Bindings)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateLabelFileBinDingBatch(c *gin.Context) {
|
||||||
|
var req struct {
|
||||||
|
Items []op.CreateLabelFileBinDingReq `json:"items" binding:"required"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil || len(req.Items) == 0 {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userObj, ok := c.Value("user").(*model.User)
|
||||||
|
if !ok {
|
||||||
|
common.ErrorStrResp(c, "user invalid", 401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type perResult struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Ok bool `json:"ok"`
|
||||||
|
ErrMsg string `json:"errMsg,omitempty"`
|
||||||
|
}
|
||||||
|
results := make([]perResult, 0, len(req.Items))
|
||||||
|
succeed := 0
|
||||||
|
|
||||||
|
for _, item := range req.Items {
|
||||||
|
if item.IsDir {
|
||||||
|
results = append(results, perResult{Name: item.Name, Ok: false, ErrMsg: "Unable to bind folder"})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := op.CreateLabelFileBinDing(item, userObj.ID); err != nil {
|
||||||
|
results = append(results, perResult{Name: item.Name, Ok: false, ErrMsg: err.Error()})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
succeed++
|
||||||
|
results = append(results, perResult{Name: item.Name, Ok: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SuccessResp(c, gin.H{
|
||||||
|
"total": len(req.Items),
|
||||||
|
"succeed": succeed,
|
||||||
|
"failed": len(req.Items) - succeed,
|
||||||
|
"results": results,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -67,10 +67,10 @@ func UpdateUser(c *gin.Context) {
|
||||||
common.ErrorStrResp(c, "cannot change role of admin user", 403)
|
common.ErrorStrResp(c, "cannot change role of admin user", 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if user.Username != req.Username {
|
//if user.Username != req.Username {
|
||||||
common.ErrorStrResp(c, "cannot change username of admin user", 403)
|
// common.ErrorStrResp(c, "cannot change username of admin user", 403)
|
||||||
return
|
// return
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Password == "" {
|
if req.Password == "" {
|
||||||
|
|
|
@ -92,6 +92,8 @@ func Init(e *gin.Engine) {
|
||||||
|
|
||||||
_fs(auth.Group("/fs"))
|
_fs(auth.Group("/fs"))
|
||||||
_task(auth.Group("/task", middlewares.AuthNotGuest))
|
_task(auth.Group("/task", middlewares.AuthNotGuest))
|
||||||
|
_label(auth.Group("/label"))
|
||||||
|
_labelFileBinding(auth.Group("/label_file_binding"))
|
||||||
admin(auth.Group("/admin", middlewares.AuthAdmin))
|
admin(auth.Group("/admin", middlewares.AuthAdmin))
|
||||||
if flags.Debug || flags.Dev {
|
if flags.Debug || flags.Dev {
|
||||||
debug(g.Group("/debug"))
|
debug(g.Group("/debug"))
|
||||||
|
@ -170,17 +172,17 @@ func admin(g *gin.RouterGroup) {
|
||||||
index.GET("/progress", middlewares.SearchIndex, handles.GetProgress)
|
index.GET("/progress", middlewares.SearchIndex, handles.GetProgress)
|
||||||
|
|
||||||
label := g.Group("/label")
|
label := g.Group("/label")
|
||||||
label.GET("/list", handles.ListLabel)
|
|
||||||
label.GET("/get", handles.GetLabel)
|
|
||||||
label.POST("/create", handles.CreateLabel)
|
label.POST("/create", handles.CreateLabel)
|
||||||
label.POST("/update", handles.UpdateLabel)
|
label.POST("/update", handles.UpdateLabel)
|
||||||
label.POST("/delete", handles.DeleteLabel)
|
label.POST("/delete", handles.DeleteLabel)
|
||||||
|
|
||||||
labelFileBinding := g.Group("/label_file_binding")
|
labelFileBinding := g.Group("/label_file_binding")
|
||||||
labelFileBinding.GET("/get", handles.GetLabelByFileName)
|
labelFileBinding.GET("/list", handles.ListLabelFileBinding)
|
||||||
labelFileBinding.GET("/get_file_by_label", handles.GetFileByLabel)
|
|
||||||
labelFileBinding.POST("/create", handles.CreateLabelFileBinDing)
|
labelFileBinding.POST("/create", handles.CreateLabelFileBinDing)
|
||||||
|
labelFileBinding.POST("/create_batch", handles.CreateLabelFileBinDingBatch)
|
||||||
labelFileBinding.POST("/delete", handles.DelLabelByFileName)
|
labelFileBinding.POST("/delete", handles.DelLabelByFileName)
|
||||||
|
labelFileBinding.POST("/restore", handles.RestoreLabelFileBinding)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func _fs(g *gin.RouterGroup) {
|
func _fs(g *gin.RouterGroup) {
|
||||||
|
@ -216,6 +218,16 @@ func _task(g *gin.RouterGroup) {
|
||||||
handles.SetupTaskRoute(g)
|
handles.SetupTaskRoute(g)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _label(g *gin.RouterGroup) {
|
||||||
|
g.GET("/list", handles.ListLabel)
|
||||||
|
g.GET("/get", handles.GetLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _labelFileBinding(g *gin.RouterGroup) {
|
||||||
|
g.GET("/get", handles.GetLabelByFileName)
|
||||||
|
g.GET("/get_file_by_label", handles.GetFileByLabel)
|
||||||
|
}
|
||||||
|
|
||||||
func Cors(r *gin.Engine) {
|
func Cors(r *gin.Engine) {
|
||||||
config := cors.DefaultConfig()
|
config := cors.DefaultConfig()
|
||||||
// config.AllowAllOrigins = true
|
// config.AllowAllOrigins = true
|
||||||
|
|
Loading…
Reference in New Issue