合并 internal → main: 退款/提现/激活/CI 全面优化 #4

Open
shanshanzhong147 wants to merge 76 commits from internal into main
10 changed files with 510 additions and 74 deletions
Showing only changes of commit 34cd1c524e - Show all commits
@@ -7,6 +7,7 @@ import (
"github.com/perfect-panel/server/internal/model/group"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/model/subscribe"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
@@ -48,6 +49,33 @@ func (l *DeleteNodeGroupLogic) DeleteNodeGroup(req *types.DeleteNodeGroupRequest
return fmt.Errorf("cannot delete group with %d associated nodes, please migrate nodes first", nodeCount)
}
var defaultSubscribeCount int64
if err := l.svcCtx.DB.Model(&subscribe.Subscribe{}).Where("node_group_id = ?", nodeGroup.Id).Count(&defaultSubscribeCount).Error; err != nil {
logger.Errorf("failed to count subscribes with default group: %v", err)
return err
}
if defaultSubscribeCount > 0 {
return fmt.Errorf("cannot delete group referenced by %d subscribes' default node group", defaultSubscribeCount)
}
var subscribeGroupCount int64
if err := l.svcCtx.DB.Model(&subscribe.Subscribe{}).Where("JSON_CONTAINS(node_group_ids, ?)", fmt.Sprintf("[%d]", nodeGroup.Id)).Count(&subscribeGroupCount).Error; err != nil {
logger.Errorf("failed to count subscribes in group: %v", err)
return err
}
if subscribeGroupCount > 0 {
return fmt.Errorf("cannot delete group referenced by %d subscribes' node group list", subscribeGroupCount)
}
var userSubscribeCount int64
if err := l.svcCtx.DB.Table("user_subscribe").Where("node_group_id = ?", nodeGroup.Id).Count(&userSubscribeCount).Error; err != nil {
logger.Errorf("failed to count user subscribes in group: %v", err)
return err
}
if userSubscribeCount > 0 {
return fmt.Errorf("cannot delete group referenced by %d user subscribes", userSubscribeCount)
}
// 使用 GORM Transaction 删除节点组
return l.svcCtx.DB.Transaction(func(tx *gorm.DB) error {
// 删除节点组
@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/csv"
"encoding/json"
"fmt"
"github.com/perfect-panel/server/internal/model/group"
@@ -52,10 +53,9 @@ func (l *ExportGroupResultLogic) ExportGroupResult(req *types.ExportGroupResultR
Email string `json:"email"`
}
var users []UserInfo
if err := l.svcCtx.DB.Raw("SELECT * FROM JSON_ARRAY(?)", detail.UserData).Scan(&users).Error; err != nil {
// 如果解析失败,尝试用标准 JSON 解析
if err := json.Unmarshal([]byte(detail.UserData), &users); err != nil {
logger.Errorf("failed to parse user data: %v", err)
continue
return nil, "", fmt.Errorf("parse group history user_data failed: %w", err)
}
// 查询节点组名称
@@ -123,7 +123,10 @@ func (l *ExportGroupResultLogic) ExportGroupResult(req *types.ExportGroupResultR
result = append(result, csvData...)
// 生成文件名
filename := fmt.Sprintf("group_result_%d.csv", req.HistoryId)
filename := "group_result_current.csv"
if req.HistoryId != nil {
filename = fmt.Sprintf("group_result_%d.csv", *req.HistoryId)
}
return result, filename, nil
}
@@ -76,16 +76,16 @@ func (l *GetGroupHistoryDetailLogic) GetGroupHistoryDetail(req *types.GetGroupHi
configSnapshot := make(map[string]interface{})
configSnapshot["group_details"] = details
// 获取配置快照(从 system_config 读取)
// 获取配置快照(从 system 读取)
var configValue string
if history.GroupMode == "average" {
l.svcCtx.DB.Table("system_config").
Where("`key` = ?", "group.average_config").
l.svcCtx.DB.Table("system").
Where("`category` = ? AND `key` = ?", "group", "average_config").
Select("value").
Scan(&configValue)
} else if history.GroupMode == "traffic" {
l.svcCtx.DB.Table("system_config").
Where("`key` = ?", "group.traffic_config").
l.svcCtx.DB.Table("system").
Where("`category` = ? AND `key` = ?", "group", "traffic_config").
Select("value").
Scan(&configValue)
}
@@ -44,9 +44,9 @@ func (l *GetGroupHistoryLogic) GetGroupHistory(req *types.GetGroupHistoryRequest
return nil, err
}
// 分页查询
offset := (req.Page - 1) * req.Size
if err := query.Order("id DESC").Offset(offset).Limit(req.Size).Find(&histories).Error; err != nil {
page, size := normalizePagination(req.Page, req.Size)
offset := (page - 1) * size
if err := query.Order("id DESC").Offset(offset).Limit(size).Find(&histories).Error; err != nil {
logger.Errorf("failed to find group histories: %v", err)
return nil, err
}
@@ -38,9 +38,9 @@ func (l *GetNodeGroupListLogic) GetNodeGroupList(req *types.GetNodeGroupListRequ
return nil, err
}
// 分页查询
offset := (req.Page - 1) * req.Size
if err := query.Order("sort ASC").Offset(offset).Limit(req.Size).Find(&nodeGroups).Error; err != nil {
page, size := normalizePagination(req.Page, req.Size)
offset := (page - 1) * size
if err := query.Order("sort ASC").Offset(offset).Limit(size).Find(&nodeGroups).Error; err != nil {
logger.Errorf("failed to find node groups: %v", err)
return nil, err
}
@@ -0,0 +1,346 @@
package group
import (
"context"
"encoding/csv"
"fmt"
"strings"
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
modelgroup "github.com/perfect-panel/server/internal/model/group"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func TestGetGroupHistoryDetailReadsConfigFromSystem(t *testing.T) {
db, mock, cleanup := newGroupTestDB(t)
defer cleanup()
now := time.Unix(1710000000, 0)
mock.ExpectQuery("FROM `group_history`").
WithArgs(int64(9), 1).
WillReturnRows(sqlmock.NewRows([]string{
"id", "group_mode", "trigger_type", "state", "total_users", "success_count", "failed_count", "start_time", "end_time", "error_message", "created_at",
}).AddRow(int64(9), "traffic", "manual", "completed", 1, 1, 0, now, now, "", now))
mock.ExpectQuery("FROM `group_history_detail`").
WithArgs(int64(9)).
WillReturnRows(sqlmock.NewRows([]string{
"id", "history_id", "node_group_id", "user_count", "node_count", "user_data", "created_at",
}).AddRow(int64(1), int64(9), int64(3), 1, 2, `[{"id":7,"email":"u@example.com"}]`, now))
mock.ExpectQuery("FROM `system`").
WithArgs("group", "traffic_config").
WillReturnRows(sqlmock.NewRows([]string{"value"}).AddRow(`{"strategy":"closed"}`))
logic := newTestGroupHistoryDetailLogic(db)
resp, err := logic.GetGroupHistoryDetail(&types.GetGroupHistoryDetailRequest{Id: 9})
if err != nil {
t.Fatalf("GetGroupHistoryDetail error: %v", err)
}
if got := resp.ConfigSnapshot["config"].(map[string]interface{})["strategy"]; got != "closed" {
t.Fatalf("config snapshot strategy = %v, want closed", got)
}
assertGroupExpectations(t, mock)
}
func TestExportGroupResultParsesHistoryUserDataJSON(t *testing.T) {
db, mock, cleanup := newGroupTestDB(t)
defer cleanup()
historyID := int64(11)
now := time.Unix(1710000000, 0)
mock.ExpectQuery("FROM `group_history_detail`").
WithArgs(historyID).
WillReturnRows(sqlmock.NewRows([]string{
"id", "history_id", "node_group_id", "user_count", "node_count", "user_data", "created_at",
}).AddRow(int64(1), historyID, int64(8), 1, 2, `[{"id":42,"email":"u@example.com"}]`, now))
mock.ExpectQuery("FROM `node_group`").
WithArgs(int64(8), 1).
WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(int64(8), "VIP"))
logic := newTestExportGroupResultLogic(db)
data, filename, err := logic.ExportGroupResult(&types.ExportGroupResultRequest{HistoryId: &historyID})
if err != nil {
t.Fatalf("ExportGroupResult error: %v", err)
}
if filename != "group_result_11.csv" {
t.Fatalf("filename = %q, want group_result_11.csv", filename)
}
records, err := csv.NewReader(strings.NewReader(strings.TrimPrefix(string(data), "\ufeff"))).ReadAll()
if err != nil {
t.Fatalf("read csv: %v", err)
}
if len(records) != 2 || strings.Join(records[1], ",") != "42,8,VIP" {
t.Fatalf("csv records = %#v, want user/group row", records)
}
assertGroupExpectations(t, mock)
}
func TestExportGroupResultRejectsInvalidHistoryUserDataJSON(t *testing.T) {
db, mock, cleanup := newGroupTestDB(t)
defer cleanup()
historyID := int64(12)
now := time.Unix(1710000000, 0)
mock.ExpectQuery("FROM `group_history_detail`").
WithArgs(historyID).
WillReturnRows(sqlmock.NewRows([]string{
"id", "history_id", "node_group_id", "user_count", "node_count", "user_data", "created_at",
}).AddRow(int64(1), historyID, int64(8), 1, 2, `{bad-json`, now))
logic := newTestExportGroupResultLogic(db)
_, _, err := logic.ExportGroupResult(&types.ExportGroupResultRequest{HistoryId: &historyID})
if err == nil || !strings.Contains(err.Error(), "parse group history user_data failed") {
t.Fatalf("ExportGroupResult error = %v, want parse error", err)
}
assertGroupExpectations(t, mock)
}
func TestDeleteNodeGroupRejectsSubscribeReferences(t *testing.T) {
tests := []struct {
name string
defaultSubscribeCount int64
subscribeGroupCount int64
userSubscribeCount int64
wantErr string
}{
{name: "subscribe default group", defaultSubscribeCount: 1, wantErr: "default node group"},
{name: "subscribe group list", subscribeGroupCount: 1, wantErr: "node group list"},
{name: "user subscribe group", userSubscribeCount: 1, wantErr: "user subscribes"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
db, mock, cleanup := newGroupTestDB(t)
defer cleanup()
expectDeleteNodeGroupBaseChecks(mock, 5)
mock.ExpectQuery("FROM `subscribe`").
WithArgs(int64(5)).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(tt.defaultSubscribeCount))
if tt.defaultSubscribeCount == 0 {
mock.ExpectQuery("FROM `subscribe`").
WithArgs("[5]").
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(tt.subscribeGroupCount))
}
if tt.defaultSubscribeCount == 0 && tt.subscribeGroupCount == 0 {
mock.ExpectQuery("FROM `user_subscribe`").
WithArgs(int64(5)).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(tt.userSubscribeCount))
}
logic := newTestDeleteNodeGroupLogic(db)
err := logic.DeleteNodeGroup(&types.DeleteNodeGroupRequest{Id: 5})
if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
t.Fatalf("DeleteNodeGroup error = %v, want contains %q", err, tt.wantErr)
}
assertGroupExpectations(t, mock)
})
}
}
func TestResetGroupsRollsBackOnFailure(t *testing.T) {
db, mock, cleanup := newGroupTestDB(t)
defer cleanup()
mock.ExpectBegin()
mock.ExpectExec("DELETE FROM `node_group`").WillReturnResult(sqlmock.NewResult(0, 1))
mock.ExpectExec("UPDATE `subscribe`").
WithArgs(int64(0), "[]").
WillReturnResult(sqlmock.NewResult(0, 1))
mock.ExpectExec("UPDATE `nodes`").
WithArgs("[]").
WillReturnError(fmt.Errorf("node update failed"))
mock.ExpectRollback()
logic := newTestResetGroupsLogic(db)
err := logic.ResetGroups()
if err == nil || !strings.Contains(err.Error(), "node update failed") {
t.Fatalf("ResetGroups error = %v, want node update failure", err)
}
assertGroupExpectations(t, mock)
}
func TestPreviewUserNodesIncludesPublicNodesInGroupMode(t *testing.T) {
db, mock, cleanup := newGroupTestDB(t)
defer cleanup()
now := time.Unix(1710000000, 0)
mock.ExpectQuery("FROM `user_subscribe`").
WithArgs(int64(100), int8(0), int8(1)).
WillReturnRows(sqlmock.NewRows([]string{"id", "user_id", "subscribe_id", "node_group_id"}).
AddRow(int64(1), int64(100), int64(10), int64(5)))
mock.ExpectQuery("FROM `subscribe`").
WithArgs(int64(10)).
WillReturnRows(sqlmock.NewRows([]string{"id", "node_group_id", "node_group_ids", "nodes", "node_tags"}).
AddRow(int64(10), int64(0), "[]", "", ""))
mock.ExpectQuery("FROM `system`").
WithArgs("group", "enabled").
WillReturnRows(sqlmock.NewRows([]string{"value"}).AddRow("true"))
mock.ExpectQuery("FROM `nodes`").
WithArgs(true).
WillReturnRows(sqlmock.NewRows([]string{
"id", "name", "tags", "port", "address", "server_id", "protocol", "enabled", "sort", "node_group_ids", "created_at", "updated_at",
}).
AddRow(int64(1), "group-node", "", uint16(443), "g.example.com", int64(1), "vless", true, 1, "[5]", now, now).
AddRow(int64(2), "public-node", "", uint16(443), "p.example.com", int64(1), "vless", true, 2, "[]", now, now))
mock.ExpectQuery("FROM `node_group`").
WithArgs(int64(5)).
WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(int64(5), "Group A"))
logic := newTestPreviewUserNodesLogic(db)
resp, err := logic.PreviewUserNodes(&types.PreviewUserNodesRequest{UserId: 100})
if err != nil {
t.Fatalf("PreviewUserNodes error: %v", err)
}
if !hasNodeGroup(resp.NodeGroups, 0, "public-node") {
t.Fatalf("PreviewUserNodes node groups = %#v, want public node group", resp.NodeGroups)
}
assertGroupExpectations(t, mock)
}
func TestTrafficRangeMatchingUsesClosedBounds(t *testing.T) {
min0, max10 := int64(0), int64(10)
min10, max20 := int64(10), int64(20)
nodeGroups := []modelgroup.NodeGroup{
{Id: 1, MinTrafficGB: &min0, MaxTrafficGB: &max10},
{Id: 2, MinTrafficGB: &min10, MaxTrafficGB: &max20},
}
tests := map[float64]int64{
0: 1,
10: 1,
15: 2,
21: 0,
}
for used, want := range tests {
if got := matchTrafficNodeGroup(used, nodeGroups); got != want {
t.Fatalf("matchTrafficNodeGroup(%v) = %d, want %d", used, got, want)
}
}
}
func TestNormalizePaginationDefaultsAndMax(t *testing.T) {
tests := []struct {
page, size int
wantPage int
wantSize int
}{
{page: 0, size: 0, wantPage: 1, wantSize: defaultPageSize},
{page: -1, size: -5, wantPage: 1, wantSize: defaultPageSize},
{page: 2, size: maxPageSize + 1, wantPage: 2, wantSize: maxPageSize},
}
for _, tt := range tests {
gotPage, gotSize := normalizePagination(tt.page, tt.size)
if gotPage != tt.wantPage || gotSize != tt.wantSize {
t.Fatalf("normalizePagination(%d, %d) = (%d, %d), want (%d, %d)", tt.page, tt.size, gotPage, gotSize, tt.wantPage, tt.wantSize)
}
}
}
func newGroupTestDB(t *testing.T) (*gorm.DB, sqlmock.Sqlmock, func()) {
t.Helper()
sqlDB, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherFunc(func(expectedSQL, actualSQL string) error {
if strings.Contains(actualSQL, expectedSQL) {
return nil
}
return fmt.Errorf("actual sql %q does not contain %q", actualSQL, expectedSQL)
})))
if err != nil {
t.Fatalf("create sqlmock: %v", err)
}
db, err := gorm.Open(mysql.New(mysql.Config{Conn: sqlDB, SkipInitializeWithVersion: true}), &gorm.Config{})
if err != nil {
_ = sqlDB.Close()
t.Fatalf("open gorm db: %v", err)
}
return db, mock, func() {
_ = sqlDB.Close()
}
}
func newTestGroupHistoryDetailLogic(db *gorm.DB) *GetGroupHistoryDetailLogic {
ctx := context.Background()
return &GetGroupHistoryDetailLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: &svc.ServiceContext{DB: db},
}
}
func newTestExportGroupResultLogic(db *gorm.DB) *ExportGroupResultLogic {
ctx := context.Background()
return &ExportGroupResultLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: &svc.ServiceContext{DB: db},
}
}
func newTestDeleteNodeGroupLogic(db *gorm.DB) *DeleteNodeGroupLogic {
ctx := context.Background()
return &DeleteNodeGroupLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: &svc.ServiceContext{DB: db},
}
}
func newTestResetGroupsLogic(db *gorm.DB) *ResetGroupsLogic {
ctx := context.Background()
return &ResetGroupsLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: &svc.ServiceContext{DB: db},
}
}
func newTestPreviewUserNodesLogic(db *gorm.DB) *PreviewUserNodesLogic {
ctx := context.Background()
return &PreviewUserNodesLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: &svc.ServiceContext{DB: db},
}
}
func expectDeleteNodeGroupBaseChecks(mock sqlmock.Sqlmock, nodeGroupId int64) {
now := time.Unix(1710000000, 0)
mock.ExpectQuery("FROM `node_group`").
WithArgs(nodeGroupId, 1).
WillReturnRows(sqlmock.NewRows([]string{"id", "name", "created_at", "updated_at"}).
AddRow(nodeGroupId, "Group", now, now))
mock.ExpectQuery("FROM `nodes`").
WithArgs(fmt.Sprintf("[%d]", nodeGroupId)).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
}
func hasNodeGroup(items []types.NodeGroupItem, id int64, nodeName string) bool {
for _, item := range items {
if item.Id != id {
continue
}
for _, n := range item.Nodes {
if n.Name == nodeName {
return true
}
}
}
return false
}
func assertGroupExpectations(t *testing.T, mock sqlmock.Sqlmock) {
t.Helper()
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatalf("unmet sql expectations: %v", err)
}
}
+37
View File
@@ -0,0 +1,37 @@
package group
import (
"fmt"
"github.com/perfect-panel/server/internal/model/node"
"gorm.io/gorm"
)
const (
defaultPageSize = 20
maxPageSize = 100
)
func normalizePagination(page, size int) (int, int) {
if page <= 0 {
page = 1
}
if size <= 0 {
size = defaultPageSize
}
if size > maxPageSize {
size = maxPageSize
}
return page, size
}
func countNodesInGroup(db *gorm.DB, nodeGroupId int64) (int, error) {
var count int64
err := db.Model(&node.Node{}).
Where("JSON_CONTAINS(node_group_ids, ?)", fmt.Sprintf("[%d]", nodeGroupId)).
Count(&count).Error
if err != nil {
return 0, err
}
return int(count), nil
}
@@ -196,10 +196,10 @@ func (l *PreviewUserNodesLogic) PreviewUserNodes(req *types.PreviewUserNodesRequ
return nil, err
}
// 6. 过滤出包含至少一个匹配节点组的节点(仅显示用户真正所在分组的节点,不包含公共节点)
// 6. 过滤出公共节点和至少一个匹配节点组的节点,与真实订阅下发保持一致
for _, n := range dbNodes {
// 节点未配置节点组(公共节点),预览时不显示
if len(n.NodeGroupIds) == 0 {
filteredNodes = append(filteredNodes, n)
continue
}
@@ -450,9 +450,13 @@ func (l *PreviewUserNodesLogic) PreviewUserNodes(req *types.PreviewUserNodesRequ
}
}
// 预览模式不显示公共节点(node_group_ids 为空的节点),只展示用户真正所在分组的节点
if len(publicNodes) > 0 {
logger.Infof("[PreviewUserNodes] skipping %d public nodes (not in user's assigned group)", len(publicNodes))
nodeGroupItems = append(nodeGroupItems, types.NodeGroupItem{
Id: 0,
Name: "公共节点",
Nodes: publicNodes,
})
logger.Infof("[PreviewUserNodes] adding public nodes group: nodes=%d", len(publicNodes))
}
} else {
@@ -679,21 +679,7 @@ func (l *RecalculateGroupLogic) executeTrafficGrouping(tx *gorm.DB, historyId in
// 将字节转换为 GB
usedTrafficGB := float64(us.UsedTraffic) / (1024 * 1024 * 1024)
// 查找匹配的流量范围(使用左闭右开区间 [Min, Max))
var targetNodeGroupId int64 = 0
for _, ng := range nodeGroups {
if ng.MinTrafficGB == nil || ng.MaxTrafficGB == nil {
continue
}
minTraffic := float64(*ng.MinTrafficGB)
maxTraffic := float64(*ng.MaxTrafficGB)
// 检查是否在区间内 [min, max)
if usedTrafficGB >= minTraffic && usedTrafficGB < maxTraffic {
targetNodeGroupId = ng.Id
break
}
}
targetNodeGroupId := matchTrafficNodeGroup(usedTrafficGB, nodeGroups)
// 如果没有匹配到任何范围,targetNodeGroupId 保持为 0(不分配节点组)
@@ -734,7 +720,14 @@ func (l *RecalculateGroupLogic) executeTrafficGrouping(tx *gorm.DB, historyId in
// 4. 创建分组历史详情记录(只统计有用户的节点组)
nodeGroupCount := make(map[int64]int) // node_group_id -> node_count
for _, ng := range nodeGroups {
nodeGroupCount[ng.Id] = 1 // 每个节点组计为1
count, err := countNodesInGroup(tx, ng.Id)
if err != nil {
l.Errorw("failed to count nodes in group",
logger.Field("node_group_id", ng.Id),
logger.Field("error", err.Error()))
return affectedCount, err
}
nodeGroupCount[ng.Id] = count
}
for nodeGroupId, userCount := range groupUserCount {
@@ -764,6 +757,20 @@ func (l *RecalculateGroupLogic) executeTrafficGrouping(tx *gorm.DB, historyId in
return affectedCount, nil
}
func matchTrafficNodeGroup(usedTrafficGB float64, nodeGroups []group.NodeGroup) int64 {
for _, ng := range nodeGroups {
if ng.MinTrafficGB == nil || ng.MaxTrafficGB == nil {
continue
}
minTraffic := float64(*ng.MinTrafficGB)
maxTraffic := float64(*ng.MaxTrafficGB)
if usedTrafficGB >= minTraffic && usedTrafficGB <= maxTraffic {
return ng.Id
}
}
return 0
}
// containsIgnoreCase checks if a string contains another substring (case-insensitive)
func containsIgnoreCase(s, substr string) bool {
if len(substr) == 0 {
+50 -39
View File
@@ -7,8 +7,10 @@ import (
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/model/subscribe"
"github.com/perfect-panel/server/internal/model/system"
"github.com/perfect-panel/server/internal/model/user"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/pkg/logger"
"gorm.io/gorm"
)
type ResetGroupsLogic struct {
@@ -27,55 +29,64 @@ func NewResetGroupsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Reset
}
func (l *ResetGroupsLogic) ResetGroups() error {
// 1. Delete all node groups
err := l.svcCtx.DB.Where("1 = 1").Delete(&group.NodeGroup{}).Error
if err != nil {
l.Errorw("Failed to delete all node groups", logger.Field("error", err.Error()))
return err
}
l.Infow("Successfully deleted all node groups")
err := l.svcCtx.DB.Transaction(func(tx *gorm.DB) error {
// 1. Delete all node groups
if err := tx.Where("1 = 1").Delete(&group.NodeGroup{}).Error; err != nil {
l.Errorw("Failed to delete all node groups", logger.Field("error", err.Error()))
return err
}
l.Infow("Successfully deleted all node groups")
// 2. Clear node_group_ids for all subscribes (products)
err = l.svcCtx.DB.Model(&subscribe.Subscribe{}).Where("1 = 1").Update("node_group_ids", "[]").Error
if err != nil {
l.Errorw("Failed to clear subscribes' node_group_ids", logger.Field("error", err.Error()))
return err
}
l.Infow("Successfully cleared all subscribes' node_group_ids")
// 2. Clear node_group_id/node_group_ids for all subscribes (products)
if err := tx.Table((&subscribe.Subscribe{}).TableName()).Where("1 = 1").Updates(map[string]interface{}{
"node_group_id": 0,
"node_group_ids": "[]",
}).Error; err != nil {
l.Errorw("Failed to clear subscribes' node groups", logger.Field("error", err.Error()))
return err
}
l.Infow("Successfully cleared all subscribes' node groups")
// 3. Clear node_group_ids for all nodes
err = l.svcCtx.DB.Model(&node.Node{}).Where("1 = 1").Update("node_group_ids", "[]").Error
if err != nil {
l.Errorw("Failed to clear nodes' node_group_ids", logger.Field("error", err.Error()))
return err
}
l.Infow("Successfully cleared all nodes' node_group_ids")
// 3. Clear node_group_ids for all nodes
if err := tx.Table((&node.Node{}).TableName()).Where("1 = 1").Update("node_group_ids", "[]").Error; err != nil {
l.Errorw("Failed to clear nodes' node_group_ids", logger.Field("error", err.Error()))
return err
}
l.Infow("Successfully cleared all nodes' node_group_ids")
// 4. Clear group history
err = l.svcCtx.DB.Where("1 = 1").Delete(&group.GroupHistory{}).Error
if err != nil {
l.Errorw("Failed to clear group history", logger.Field("error", err.Error()))
// Non-critical error, continue anyway
} else {
// 4. Clear user_subscribe node_group_id
if err := tx.Table((&user.Subscribe{}).TableName()).Where("1 = 1").Update("node_group_id", 0).Error; err != nil {
l.Errorw("Failed to clear user subscribes' node_group_id", logger.Field("error", err.Error()))
return err
}
l.Infow("Successfully cleared all user subscribes' node_group_id")
// 5. Clear group history
if err := tx.Where("1 = 1").Delete(&group.GroupHistory{}).Error; err != nil {
l.Errorw("Failed to clear group history", logger.Field("error", err.Error()))
return err
}
l.Infow("Successfully cleared group history")
}
// 7. Clear group history details
err = l.svcCtx.DB.Where("1 = 1").Delete(&group.GroupHistoryDetail{}).Error
if err != nil {
l.Errorw("Failed to clear group history details", logger.Field("error", err.Error()))
// Non-critical error, continue anyway
} else {
// 6. Clear group history details
if err := tx.Where("1 = 1").Delete(&group.GroupHistoryDetail{}).Error; err != nil {
l.Errorw("Failed to clear group history details", logger.Field("error", err.Error()))
return err
}
l.Infow("Successfully cleared group history details")
}
// 5. Delete all group config settings
err = l.svcCtx.DB.Where("`category` = ?", "group").Delete(&system.System{}).Error
// 7. Delete all group config settings
if err := tx.Where("`category` = ?", "group").Delete(&system.System{}).Error; err != nil {
l.Errorw("Failed to delete group config", logger.Field("error", err.Error()))
return err
}
l.Infow("Successfully deleted all group config settings")
return nil
})
if err != nil {
l.Errorw("Failed to delete group config", logger.Field("error", err.Error()))
return err
}
l.Infow("Successfully deleted all group config settings")
l.Infow("Group reset completed successfully")
return nil