diff --git a/backend/internal/handler/ticket.go b/backend/internal/handler/ticket.go index 11836cb..cb29b9e 100644 --- a/backend/internal/handler/ticket.go +++ b/backend/internal/handler/ticket.go @@ -1,6 +1,7 @@ package handler import ( + "context" "encoding/json" "fmt" "net/http" @@ -211,23 +212,76 @@ func CreateTicket(c *gin.Context) { return } - // Auto-create approval record: step 1, approver = instance owner or admin - stepName := "审核" - stepType := "audit" - approverRole := "admin" - // Find first admin as default approver - var adminUser model.User - if err := model.DB.Where("role = ? AND is_active = 1", "admin").First(&adminUser).Error; err == nil { - adminID := adminUser.ID - approval := model.TicketApproval{ - TicketID: ticket.ID, - StepNumber: 1, - StepName: &stepName, - StepType: &stepType, - ApproverID: &adminID, - ApproverRole: &approverRole, + // Create approval records based on workflow or default to admin + if input.WorkflowID != nil && *input.WorkflowID > 0 { + // Load workflow steps + var wfSteps []model.WorkflowStep + model.DB.Where("workflow_id = ?", *input.WorkflowID).Order("step_number").Find(&wfSteps) + for _, ws := range wfSteps { + if ws.StepType == "submit" { + continue // submit step needs no approver + } + // Parse approver_ids from JSON + var approverIDs []int + if ws.ApproverIDs != nil && *ws.ApproverIDs != "" { + json.Unmarshal([]byte(*ws.ApproverIDs), &approverIDs) + } + // Create one approval record per approver (any one can approve) + if len(approverIDs) > 0 { + for _, uid := range approverIDs { + uidCopy := uid + stepName := ws.Name + stepType := ws.StepType + approval := model.TicketApproval{ + TicketID: ticket.ID, + StepNumber: ws.StepNumber, + StepName: &stepName, + StepType: &stepType, + ApproverID: &uidCopy, + } + model.DB.Create(&approval) + } + } else { + // No specific users — use role-based fallback + stepName := ws.Name + stepType := ws.StepType + var roleUser model.User + role := "admin" + if ws.ApproverRole != nil && *ws.ApproverRole != "" { + role = *ws.ApproverRole + } + if model.DB.Where("role = ? AND is_active = 1", role).First(&roleUser).Error == nil { + rid := roleUser.ID + approval := model.TicketApproval{ + TicketID: ticket.ID, + StepNumber: ws.StepNumber, + StepName: &stepName, + StepType: &stepType, + ApproverID: &rid, + ApproverRole: &role, + } + model.DB.Create(&approval) + } + } + } + } else { + // No workflow — default: first admin as approver + stepName := "审核" + stepType := "audit" + approverRole := "admin" + var adminUser model.User + if err := model.DB.Where("role = ? AND is_active = 1", "admin").First(&adminUser).Error; err == nil { + adminID := adminUser.ID + approval := model.TicketApproval{ + TicketID: ticket.ID, + StepNumber: 1, + StepName: &stepName, + StepType: &stepType, + ApproverID: &adminID, + ApproverRole: &approverRole, + } + model.DB.Create(&approval) } - model.DB.Create(&approval) } c.JSON(http.StatusCreated, ticket) @@ -339,7 +393,36 @@ func ExecuteTicket(c *gin.Context) { var execErr string if ticket.DBType == "redis" { - execErr = "Redis command execution not yet supported in ticket workflow" + // Execute Redis command (write operations allowed in ticket workflow) + redisDB := 0 + if ticket.TargetDatabase != "" { + if n, err := strconv.Atoi(strings.TrimPrefix(ticket.TargetDatabase, "db")); err == nil && n >= 0 { + redisDB = n + } + } + client, err := getRedisClient(&instance, redisDB) + if err != nil { + execErr = fmt.Sprintf("Redis connection failed: %v", err) + } else { + defer client.Close() + ctx := context.Background() + parts := parseCommandParts(ticket.SQLContent) + if len(parts) == 0 { + execErr = "empty command" + } else { + cmd := strings.ToUpper(parts[0]) + args := make([]interface{}, len(parts)) + for i, p := range parts { + args[i] = p + } + result, err := client.Do(ctx, args...).Result() + if err != nil { + execErr = fmt.Sprintf("%s failed: %v", cmd, err) + } else { + execResult = fmt.Sprintf("[OK] %s → %v", ticket.SQLContent, result) + } + } + } } else { // MySQL/PG execution db, err := getDBConnection(&instance, ticket.TargetDatabase)