package handlers import ( "database/sql" "encoding/json" "net/http" "strconv" "github.com/gin-gonic/gin" "armature/store" "armature/workflow" ) // ── Instance Handlers ─────────────────────── // WorkflowInstanceHandler manages workflow instance HTTP endpoints. type WorkflowInstanceHandler struct { engine *workflow.Engine stores store.Stores } // NewWorkflowInstanceHandler creates an instance handler. func NewWorkflowInstanceHandler(engine *workflow.Engine, stores store.Stores) *WorkflowInstanceHandler { return &WorkflowInstanceHandler{engine: engine, stores: stores} } // Start creates a new workflow instance. // POST /api/v1/workflows/:id/instances func (h *WorkflowInstanceHandler) Start(c *gin.Context) { workflowID := c.Param("id") userID := c.GetString("user_id") var body struct { Data json.RawMessage `json:"data"` Metadata json.RawMessage `json:"metadata"` } if err := c.ShouldBindJSON(&body); err != nil && err.Error() != "EOF" { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"}) return } if len(body.Data) == 0 { body.Data = json.RawMessage(`{}`) } inst, err := h.engine.Start(c.Request.Context(), workflowID, body.Data, userID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, inst) } // ListInstances returns instances for a workflow. // GET /api/v1/workflows/:id/instances?status=&limit=&offset= func (h *WorkflowInstanceHandler) ListInstances(c *gin.Context) { workflowID := c.Param("id") status := c.Query("status") opts := store.ListOptions{} if l := c.Query("limit"); l != "" { opts.Limit, _ = strconv.Atoi(l) } if o := c.Query("offset"); o != "" { opts.Offset, _ = strconv.Atoi(o) } result, err := h.stores.Workflows.ListInstances(c.Request.Context(), workflowID, status, opts) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list instances"}) return } if result == nil { c.JSON(http.StatusOK, gin.H{"data": []struct{}{}}) return } c.JSON(http.StatusOK, gin.H{"data": result}) } // GetInstance returns a single instance. // GET /api/v1/workflows/:id/instances/:iid func (h *WorkflowInstanceHandler) GetInstance(c *gin.Context) { inst, err := h.stores.Workflows.GetInstance(c.Request.Context(), c.Param("iid")) if err != nil { if err == sql.ErrNoRows { c.JSON(http.StatusNotFound, gin.H{"error": "instance not found"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // Verify it belongs to the requested workflow if inst.WorkflowID != c.Param("id") { c.JSON(http.StatusNotFound, gin.H{"error": "instance not found"}) return } c.JSON(http.StatusOK, inst) } // Advance moves an instance to the next stage. // POST /api/v1/workflows/:id/instances/:iid/advance func (h *WorkflowInstanceHandler) Advance(c *gin.Context) { userID := c.GetString("user_id") var body struct { Data json.RawMessage `json:"data"` } if err := c.ShouldBindJSON(&body); err != nil && err.Error() != "EOF" { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"}) return } inst, err := h.engine.Advance(c.Request.Context(), c.Param("iid"), body.Data, userID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, inst) } // Cancel terminates an active instance. // POST /api/v1/workflows/:id/instances/:iid/cancel func (h *WorkflowInstanceHandler) Cancel(c *gin.Context) { userID := c.GetString("user_id") if err := h.engine.Cancel(c.Request.Context(), c.Param("iid"), userID); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"cancelled": true}) }