- Rename Go module switchboard-core → armature (155+ files) - Rename Docker image → gobha/armature - Rename K8s resources, secrets, deployments - Rename Prometheus metrics switchboard_* → armature_* - Rename env vars SWITCHBOARD_ADMIN_* → ARMATURE_ADMIN_* - Rename DB names switchboard_core* → armature* - Update all frontend branding, notification templates, docs - Update CI scripts, e2e tests, Keycloak realm, nginx conf - Rename scripts/switchboard-ca.sh → scripts/armature-ca.sh - Rename k8s/switchboard.yaml → k8s/armature.yaml - Rename chart alerting/dashboard files - Fix: DockerHub push uses env: binding for secret injection - Helm chart updated (name, labels, template functions, dashboard, alerting) - Replace favicon/icon assets with Armature brand No functional changes. Pure mechanical rename + CI fix. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
135 lines
3.7 KiB
Go
135 lines
3.7 KiB
Go
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})
|
|
}
|