diff --git a/adapter/adapter.go b/adapter/adapter.go index a29ccae..1acc674 100644 --- a/adapter/adapter.go +++ b/adapter/adapter.go @@ -102,6 +102,7 @@ func (adapter *Adapter) Proxies(servers []*node.Node) ([]Proxy, error) { for _, protocol := range protocols { if protocol.Type == item.Protocol { proxies = append(proxies, Proxy{ + Sort: item.Sort, Name: item.Name, Server: item.Address, Port: item.Port, diff --git a/adapter/client.go b/adapter/client.go index 0922e94..340fdae 100644 --- a/adapter/client.go +++ b/adapter/client.go @@ -11,6 +11,7 @@ import ( ) type Proxy struct { + Sort int Name string Server string Port uint16 diff --git a/apis/admin/server.api b/apis/admin/server.api index 6432f97..34d36bd 100644 --- a/apis/admin/server.api +++ b/apis/admin/server.api @@ -165,9 +165,7 @@ type ( Message string `json:"message,omitempty"` } ResetSortRequest { - Page int `json:"page"` - Size int `json:"size"` - Sort []int64 `json:"sort"` + Sort []SortItem `json:"sort"` } ) diff --git a/internal/logic/admin/server/resetSortWithNodeLogic.go b/internal/logic/admin/server/resetSortWithNodeLogic.go index 977db9f..3866f54 100644 --- a/internal/logic/admin/server/resetSortWithNodeLogic.go +++ b/internal/logic/admin/server/resetSortWithNodeLogic.go @@ -3,9 +3,13 @@ package server import ( "context" + "github.com/perfect-panel/server/internal/model/node" "github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/types" "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/xerr" + "github.com/pkg/errors" + "gorm.io/gorm" ) type ResetSortWithNodeLogic struct { @@ -14,7 +18,7 @@ type ResetSortWithNodeLogic struct { svcCtx *svc.ServiceContext } -// Reset node sort +// NewResetSortWithNodeLogic Reset node sort func NewResetSortWithNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ResetSortWithNodeLogic { return &ResetSortWithNodeLogic{ Logger: logger.WithContext(ctx), @@ -24,7 +28,59 @@ func NewResetSortWithNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) } func (l *ResetSortWithNodeLogic) ResetSortWithNode(req *types.ResetSortRequest) error { - // todo: add your logic here and delete this line + err := l.svcCtx.NodeModel.Transaction(l.ctx, func(db *gorm.DB) error { + // find all servers id + var existingIDs []int64 + db.Model(&node.Node{}).Select("id").Find(&existingIDs) + // check if the id is valid + validIDMap := make(map[int64]bool) + for _, id := range existingIDs { + validIDMap[id] = true + } + // check if the sort is valid + var validItems []types.SortItem + for _, item := range req.Sort { + if validIDMap[item.Id] { + validItems = append(validItems, item) + } + } + // query all servers + var servers []*node.Node + db.Model(&node.Node{}).Order("sort ASC").Find(&servers) + // create a map of the current sort + currentSortMap := make(map[int64]int64) + for _, item := range servers { + currentSortMap[item.Id] = int64(item.Sort) + } + // new sort map + newSortMap := make(map[int64]int64) + for _, item := range validItems { + newSortMap[item.Id] = item.Sort + } + + var itemsToUpdate []types.SortItem + for _, item := range validItems { + if oldSort, exists := currentSortMap[item.Id]; exists && oldSort != item.Sort { + itemsToUpdate = append(itemsToUpdate, item) + } + } + for _, item := range itemsToUpdate { + s, err := l.svcCtx.NodeModel.FindOneNode(l.ctx, item.Id) + if err != nil { + return err + } + s.Sort = int(item.Sort) + if err = l.svcCtx.NodeModel.UpdateNode(l.ctx, s, db); err != nil { + l.Errorw("[NodeSort] Update Database Error: ", logger.Field("error", err.Error()), logger.Field("id", item.Id), logger.Field("sort", item.Sort)) + return err + } + } + return nil + }) + if err != nil { + l.Errorw("[NodeSort] Update Database Error: ", logger.Field("error", err.Error())) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), err.Error()) + } return nil } diff --git a/internal/logic/admin/server/resetSortWithServerLogic.go b/internal/logic/admin/server/resetSortWithServerLogic.go index 16bffbe..3fbe237 100644 --- a/internal/logic/admin/server/resetSortWithServerLogic.go +++ b/internal/logic/admin/server/resetSortWithServerLogic.go @@ -3,9 +3,13 @@ package server import ( "context" + "github.com/perfect-panel/server/internal/model/node" "github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/types" "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/xerr" + "github.com/pkg/errors" + "gorm.io/gorm" ) type ResetSortWithServerLogic struct { @@ -24,5 +28,59 @@ func NewResetSortWithServerLogic(ctx context.Context, svcCtx *svc.ServiceContext } func (l *ResetSortWithServerLogic) ResetSortWithServer(req *types.ResetSortRequest) error { + err := l.svcCtx.NodeModel.Transaction(l.ctx, func(db *gorm.DB) error { + // find all servers id + var existingIDs []int64 + db.Model(&node.Server{}).Select("id").Find(&existingIDs) + // check if the id is valid + validIDMap := make(map[int64]bool) + for _, id := range existingIDs { + validIDMap[id] = true + } + // check if the sort is valid + var validItems []types.SortItem + for _, item := range req.Sort { + if validIDMap[item.Id] { + validItems = append(validItems, item) + } + } + // query all servers + var servers []*node.Server + db.Model(&node.Server{}).Order("sort ASC").Find(&servers) + // create a map of the current sort + currentSortMap := make(map[int64]int64) + for _, item := range servers { + currentSortMap[item.Id] = int64(item.Sort) + } + + // new sort map + newSortMap := make(map[int64]int64) + for _, item := range validItems { + newSortMap[item.Id] = item.Sort + } + + var itemsToUpdate []types.SortItem + for _, item := range validItems { + if oldSort, exists := currentSortMap[item.Id]; exists && oldSort != item.Sort { + itemsToUpdate = append(itemsToUpdate, item) + } + } + for _, item := range itemsToUpdate { + s, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, item.Id) + if err != nil { + return err + } + s.Sort = int(item.Sort) + if err = l.svcCtx.NodeModel.UpdateServer(l.ctx, s, db); err != nil { + l.Errorw("[NodeSort] Update Database Error: ", logger.Field("error", err.Error()), logger.Field("id", item.Id), logger.Field("sort", item.Sort)) + return err + } + } + return nil + }) + if err != nil { + l.Errorw("[NodeSort] Update Database Error: ", logger.Field("error", err.Error())) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), err.Error()) + } return nil } diff --git a/internal/model/node/node.go b/internal/model/node/node.go index 10d58a1..89d665d 100644 --- a/internal/model/node/node.go +++ b/internal/model/node/node.go @@ -3,6 +3,7 @@ package node import ( "time" + "github.com/perfect-panel/server/pkg/logger" "gorm.io/gorm" ) @@ -35,3 +36,47 @@ func (n *Node) BeforeCreate(tx *gorm.DB) error { } return nil } + +func (n *Node) BeforeDelete(tx *gorm.DB) error { + if err := tx.Exec("UPDATE `nodes` SET sort = sort - 1 WHERE sort > ?", n.Sort).Error; err != nil { + return err + } + return nil +} + +func (n *Node) BeforeUpdate(tx *gorm.DB) error { + var count int64 + if err := tx.Set("gorm:query_option", "FOR UPDATE").Model(&Server{}). + Where("sort = ? AND id != ?", n.Sort, n.Id).Count(&count).Error; err != nil { + return err + } + if count > 1 { + // reorder sort + if err := reorderSortWithNode(tx); err != nil { + logger.Errorf("[Server] BeforeUpdate reorderSort error: %v", err.Error()) + return err + } + // get max sort + var maxSort int + if err := tx.Model(&Server{}).Select("MAX(sort)").Scan(&maxSort).Error; err != nil { + return err + } + n.Sort = maxSort + 1 + } + return nil +} + +func reorderSortWithNode(tx *gorm.DB) error { + var nodes []Node + if err := tx.Order("sort, id").Find(&nodes).Error; err != nil { + return err + } + for i, node := range nodes { + if node.Sort != i+1 { + if err := tx.Exec("UPDATE `nodes` SET sort = ? WHERE id = ?", i+1, node.Id).Error; err != nil { + return err + } + } + } + return nil +} diff --git a/internal/model/node/server.go b/internal/model/node/server.go index dc75a70..ef230a7 100644 --- a/internal/model/node/server.go +++ b/internal/model/node/server.go @@ -4,6 +4,7 @@ import ( "encoding/json" "time" + "github.com/perfect-panel/server/pkg/logger" "github.com/pkg/errors" "gorm.io/gorm" ) @@ -37,6 +38,35 @@ func (m *Server) BeforeCreate(tx *gorm.DB) error { return nil } +func (m *Server) BeforeDelete(tx *gorm.DB) error { + if err := tx.Exec("UPDATE `servers` SET sort = sort - 1 WHERE sort > ?", m.Sort).Error; err != nil { + return err + } + return nil +} + +func (m *Server) BeforeUpdate(tx *gorm.DB) error { + var count int64 + if err := tx.Set("gorm:query_option", "FOR UPDATE").Model(&Server{}). + Where("sort = ? AND id != ?", m.Sort, m.Id).Count(&count).Error; err != nil { + return err + } + if count > 1 { + // reorder sort + if err := reorderSortWithServer(tx); err != nil { + logger.Errorf("[Server] BeforeUpdate reorderSort error: %v", err.Error()) + return err + } + // get max sort + var maxSort int + if err := tx.Model(&Server{}).Select("MAX(sort)").Scan(&maxSort).Error; err != nil { + return err + } + m.Sort = maxSort + 1 + } + return nil +} + // MarshalProtocols Marshal server protocols to json func (m *Server) MarshalProtocols(list []Protocol) error { var validate = make(map[string]bool) @@ -118,3 +148,18 @@ func (m *Protocol) Unmarshal(data []byte) error { } return json.Unmarshal(data, &aux) } + +func reorderSortWithServer(tx *gorm.DB) error { + var servers []Server + if err := tx.Order("sort, id").Find(&servers).Error; err != nil { + return err + } + for i, server := range servers { + if server.Sort != i+1 { + if err := tx.Exec("UPDATE `servers` SET sort = ? WHERE id = ?", i+1, server.Id).Error; err != nil { + return err + } + } + } + return nil +} diff --git a/internal/types/types.go b/internal/types/types.go index 4af8027..141e903 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -1608,9 +1608,7 @@ type ResetPasswordRequest struct { } type ResetSortRequest struct { - Page int `json:"page"` - Size int `json:"size"` - Sort []int64 `json:"sort"` + Sort []SortItem `json:"sort"` } type ResetSubscribeLog struct {