package handler import ( "fmt" "net/http" "time" "sql-platform/internal/middleware" "sql-platform/internal/model" "github.com/gin-gonic/gin" ) // GET /api/v1/tickets/statistics func TicketStatistics(c *gin.Context) { userID, _, role := middleware.GetCurrentUser(c) type statusCount struct { Status string `json:"status"` Count int64 `json:"count"` } var counts []statusCount query := model.DB.Model(&model.Ticket{}).Select("status, count(*) as count") // Filter by user role switch role { case "developer": query = query.Where("applicant_id = ?", userID) case "dba", "auditor": // DBA sees tickets they submitted + tickets they need to approve query = query.Where( "applicant_id = ? OR id IN (SELECT ticket_id FROM ticket_approvals WHERE approver_id = ?)", userID, userID, ) // admin sees everything } query.Group("status").Scan(&counts) stats := model.TicketStatistics{} for _, c := range counts { switch c.Status { case "pending": stats.Pending = int(c.Count) case "reviewing": stats.Reviewing = int(c.Count) case "executing": stats.Executing = int(c.Count) case "completed": stats.Completed = int(c.Count) case "failed": stats.Failed = int(c.Count) case "rejected": stats.Rejected = int(c.Count) } } stats.Total = stats.Pending + stats.Reviewing + stats.Executing + stats.Completed + stats.Failed + stats.Rejected c.JSON(http.StatusOK, stats) } // GET /api/v1/tickets func ListTickets(c *gin.Context) { userID, _, role := middleware.GetCurrentUser(c) status := c.Query("status") page := 1 limit := 20 query := model.DB.Model(&model.Ticket{}).Order("id DESC") // Filter by user role switch role { case "developer": query = query.Where("applicant_id = ?", userID) case "dba", "auditor": query = query.Where( "applicant_id = ? OR id IN (SELECT ticket_id FROM ticket_approvals WHERE approver_id = ?)", userID, userID, ) } if status != "" && status != "all" { query = query.Where("status = ?", status) } var total int64 query.Count(&total) var tickets []model.Ticket query.Offset((page - 1) * limit).Limit(limit).Find(&tickets) c.JSON(http.StatusOK, gin.H{ "items": tickets, "total": total, "page": page, "limit": limit, }) } // GET /api/v1/tickets/:id func GetTicket(c *gin.Context) { id := c.Param("id") var ticket model.Ticket if err := model.DB.First(&ticket, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"detail": "Ticket not found"}) return } // Get approvals var approvals []model.TicketApproval model.DB.Where("ticket_id = ?", id).Order("step_number").Find(&approvals) // Get SQL items var sqlItems []model.TicketSQLItem model.DB.Where("ticket_id = ?", id).Order("seq").Find(&sqlItems) c.JSON(http.StatusOK, gin.H{ "ticket": ticket, "approvals": approvals, "sql_items": sqlItems, }) } // POST /api/v1/tickets func CreateTicket(c *gin.Context) { userID, _, _ := middleware.GetCurrentUser(c) var input struct { Title string `json:"title" binding:"required"` DBType string `json:"db_type" binding:"required"` InstanceID int `json:"instance_id" binding:"required"` TargetDatabase string `json:"target_database" binding:"required"` SQLContent string `json:"sql_content" binding:"required"` RollbackSQL *string `json:"rollback_sql"` Priority *string `json:"priority"` Reason *string `json:"reason"` WorkflowID *int `json:"workflow_id"` RiskLevel *string `json:"risk_level"` ExecStrategy *string `json:"exec_strategy"` } if err := c.ShouldBindJSON(&input); err != nil { c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) return } status := "pending" priority := "medium" riskLevel := "low" execStrategy := "stop_on_error" if input.Priority != nil { priority = *input.Priority } if input.RiskLevel != nil { riskLevel = *input.RiskLevel } if input.ExecStrategy != nil { execStrategy = *input.ExecStrategy } // Generate ticket number now := time.Now() ticketNo := now.Format("20060102") + "-" + fmt.Sprintf("%04d", time.Now().UnixNano()%10000) ticket := model.Ticket{ Title: input.Title, DBType: input.DBType, InstanceID: input.InstanceID, TargetDatabase: input.TargetDatabase, SQLContent: input.SQLContent, RollbackSQL: input.RollbackSQL, Status: &status, Priority: &priority, ApplicantID: userID, Reason: input.Reason, WorkflowID: input.WorkflowID, RiskLevel: &riskLevel, ExecStrategy: &execStrategy, TicketNo: &ticketNo, } if err := model.DB.Create(&ticket).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()}) return } c.JSON(http.StatusCreated, ticket) } // POST /api/v1/tickets/:id/approve func ApproveTicket(c *gin.Context) { id := c.Param("id") userID, _, _ := middleware.GetCurrentUser(c) var ticket model.Ticket if err := model.DB.First(&ticket, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"detail": "Ticket not found"}) return } var input struct { Action string `json:"action" binding:"required"` // approve, reject Comment *string `json:"comment"` } if err := c.ShouldBindJSON(&input); err != nil { c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) return } now := time.Now() // Update approval record var approval model.TicketApproval if err := model.DB.Where("ticket_id = ? AND approver_id = ? AND action IS NULL", id, userID).First(&approval).Error; err != nil { c.JSON(http.StatusBadRequest, gin.H{"detail": "No pending approval for you"}) return } approval.Action = &input.Action approval.Comment = input.Comment approval.ActualApproverID = &userID approval.CompletedAt = &now model.DB.Save(&approval) // Update ticket status newStatus := "approved" if input.Action == "reject" { newStatus = "rejected" } else if input.Action == "approve" { // Check if there are more pending approvals var pendingCount int64 model.DB.Model(&model.TicketApproval{}).Where("ticket_id = ? AND action IS NULL", id).Count(&pendingCount) if pendingCount > 0 { newStatus = "reviewing" } } ticket.Status = &newStatus ticket.ReviewTime = &now model.DB.Save(&ticket) c.JSON(http.StatusOK, gin.H{"ticket": ticket, "approval": approval}) } // POST /api/v1/tickets/:id/execute func ExecuteTicket(c *gin.Context) { id := c.Param("id") var ticket model.Ticket if err := model.DB.First(&ticket, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"detail": "Ticket not found"}) return } status := "executing" ticket.Status = &status now := time.Now() ticket.ExecuteTime = &now model.DB.Save(&ticket) // TODO: actual SQL execution c.JSON(http.StatusOK, gin.H{"message": "Execution started", "ticket_id": ticket.ID}) }