package deployment import ( "encoding/json" "net/http" "strconv" "github.com/gin-gonic/gin" "gorm.io/gorm" "onixbyte.com/pipely/internal/database" "onixbyte.com/pipely/internal/model" ) type Handler struct { db *gorm.DB } func NewHandler(db *gorm.DB) *Handler { return &Handler{db: db} } func (h *Handler) Create(c *gin.Context) { var req model.CreateDeploymentRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if req.TargetType != model.TargetAll && req.TargetType != model.TargetDevice && req.TargetType != model.TargetGroup { c.JSON(http.StatusBadRequest, gin.H{"error": "targetType must be all, device, or group"}) return } if req.RolloutStrategy == "" { req.RolloutStrategy = model.RolloutImmediate } if req.MaxConcurrent <= 0 { req.MaxConcurrent = 50 } _, err := database.GetArtefactByID(h.db, req.ArtefactID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Artefact not found"}) return } deployment := &model.Deployment{ ArtefactID: req.ArtefactID, Name: req.Name, Description: req.Description, TargetType: req.TargetType, TargetSpec: req.TargetSpec, RolloutStrategy: req.RolloutStrategy, MaxConcurrent: req.MaxConcurrent, Status: model.DeploymentStatusDraft, } err = h.db.Transaction(func(tx *gorm.DB) error { if err := database.CreateDeployment(tx, deployment); err != nil { return err } deviceIDs, err := h.resolveTargets(deployment) if err != nil { return err } return database.CreateDeploymentDevices(tx, deployment.ID, deviceIDs) }) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, deployment) } func (h *Handler) resolveTargets(d *model.Deployment) ([]string, error) { switch d.TargetType { case model.TargetAll: devices, _, err := database.ListDevices(h.db, "", "", 1, 100000) if err != nil { return nil, err } ids := make([]string, len(devices)) for i, dev := range devices { ids[i] = dev.DeviceID } return ids, nil case model.TargetDevice: var spec struct { DeviceIDs []string `json:"deviceIds"` } if err := json.Unmarshal([]byte(d.TargetSpec), &spec); err != nil { return nil, err } return spec.DeviceIDs, nil case model.TargetGroup: var spec struct { GroupName string `json:"groupName"` } if err := json.Unmarshal([]byte(d.TargetSpec), &spec); err != nil { return nil, err } devices, _, err := database.ListDevices(h.db, "", spec.GroupName, 1, 100000) if err != nil { return nil, err } ids := make([]string, len(devices)) for i, dev := range devices { ids[i] = dev.DeviceID } return ids, nil default: return nil, nil } } func (h *Handler) Activate(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) return } deployment, err := database.GetDeploymentByID(h.db, id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Deployment not found"}) return } if deployment.Status != model.DeploymentStatusDraft { c.JSON(http.StatusBadRequest, gin.H{"error": "Only draft deployments can be activated"}) return } if err := database.UpdateDeploymentStatus(h.db, id, model.DeploymentStatusActive); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } deployment.Status = model.DeploymentStatusActive c.JSON(http.StatusOK, deployment) } func (h *Handler) Pause(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) return } deployment, err := database.GetDeploymentByID(h.db, id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Deployment not found"}) return } if deployment.Status != model.DeploymentStatusActive { c.JSON(http.StatusBadRequest, gin.H{"error": "Only active deployments can be paused"}) return } if err := database.UpdateDeploymentStatus(h.db, id, model.DeploymentStatusPaused); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } deployment.Status = model.DeploymentStatusPaused c.JSON(http.StatusOK, deployment) } func (h *Handler) List(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20")) status := c.Query("status") if page < 1 { page = 1 } if pageSize < 1 || pageSize > 100 { pageSize = 20 } deployments, total, err := database.ListDeployments(h.db, status, page, pageSize) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, model.PaginatedResponse{ Data: deployments, Total: total, Page: page, PageSize: pageSize, }) } func (h *Handler) Get(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) return } deployment, err := database.GetDeploymentByID(h.db, id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Deployment not found"}) return } stats, _ := database.GetDeploymentDeviceStats(h.db, id) c.JSON(http.StatusOK, gin.H{ "deployment": deployment, "stats": stats, }) } func (h *Handler) ListDevices(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) return } page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "50")) status := c.Query("status") if page < 1 { page = 1 } if pageSize < 1 || pageSize > 200 { pageSize = 50 } devices, total, err := database.GetDeploymentDevices(h.db, id, status, page, pageSize) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, model.PaginatedResponse{ Data: devices, Total: total, Page: page, PageSize: pageSize, }) } func (h *Handler) Rollback(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid deployment id"}) return } var req struct { DeviceID string `json:"deviceId" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := database.UpdateDeploymentDeviceStatus(h.db, id, req.DeviceID, model.DeployDeviceStatusRolledBack, "Rolled back by operator"); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"status": "rolled_back"}) } func (h *Handler) UpdateStatus(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid deployment id"}) return } var req struct { DeviceID string `json:"deviceId" binding:"required"` Status string `json:"status" binding:"required"` Error string `json:"error"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := database.UpdateDeploymentDeviceStatus(h.db, id, req.DeviceID, req.Status, req.Error); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"status": "updated"}) }