Files
mq/dag/workflow_api.go
2025-09-18 13:36:24 +05:45

346 lines
8.6 KiB
Go

package dag
import (
"strconv"
"time"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
)
// WorkflowAPI provides HTTP handlers for workflow management on top of DAG
type WorkflowAPI struct {
enhancedDAG *EnhancedDAG
}
// NewWorkflowAPI creates a new workflow API handler
func NewWorkflowAPI(enhancedDAG *EnhancedDAG) *WorkflowAPI {
return &WorkflowAPI{
enhancedDAG: enhancedDAG,
}
}
// RegisterWorkflowRoutes registers all workflow routes with Fiber app
func (api *WorkflowAPI) RegisterWorkflowRoutes(app *fiber.App) {
v1 := app.Group("/api/v1/workflows")
// Workflow definition routes
v1.Post("/", api.CreateWorkflow)
v1.Get("/", api.ListWorkflows)
v1.Get("/:id", api.GetWorkflow)
v1.Put("/:id", api.UpdateWorkflow)
v1.Delete("/:id", api.DeleteWorkflow)
// Execution routes
v1.Post("/:id/execute", api.ExecuteWorkflow)
v1.Get("/:id/executions", api.ListWorkflowExecutions)
v1.Get("/executions", api.ListAllExecutions)
v1.Get("/executions/:executionId", api.GetExecution)
v1.Post("/executions/:executionId/cancel", api.CancelExecution)
// Management routes
v1.Get("/health", api.HealthCheck)
v1.Get("/metrics", api.GetMetrics)
}
// CreateWorkflow creates a new workflow definition
func (api *WorkflowAPI) CreateWorkflow(c *fiber.Ctx) error {
var definition WorkflowDefinition
if err := c.BodyParser(&definition); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request body",
})
}
// Set ID if not provided
if definition.ID == "" {
definition.ID = uuid.New().String()
}
// Set version if not provided
if definition.Version == "" {
definition.Version = "1.0.0"
}
// Set timestamps
now := time.Now()
definition.CreatedAt = now
definition.UpdatedAt = now
if err := api.enhancedDAG.RegisterWorkflow(c.Context(), &definition); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": err.Error(),
})
}
return c.Status(fiber.StatusCreated).JSON(definition)
}
// ListWorkflows lists workflow definitions with filtering
func (api *WorkflowAPI) ListWorkflows(c *fiber.Ctx) error {
workflows := api.enhancedDAG.ListWorkflows()
// Apply filters if provided
status := c.Query("status")
if status != "" {
filtered := make([]*WorkflowDefinition, 0)
for _, w := range workflows {
if string(w.Status) == status {
filtered = append(filtered, w)
}
}
workflows = filtered
}
// Apply pagination
limit, _ := strconv.Atoi(c.Query("limit", "10"))
offset, _ := strconv.Atoi(c.Query("offset", "0"))
total := len(workflows)
start := offset
end := offset + limit
if start > total {
start = total
}
if end > total {
end = total
}
pagedWorkflows := workflows[start:end]
return c.JSON(fiber.Map{
"workflows": pagedWorkflows,
"total": total,
"limit": limit,
"offset": offset,
})
}
// GetWorkflow retrieves a workflow definition by ID
func (api *WorkflowAPI) GetWorkflow(c *fiber.Ctx) error {
id := c.Params("id")
if id == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Workflow ID is required",
})
}
workflow, err := api.enhancedDAG.GetWorkflow(id)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"error": err.Error(),
})
}
return c.JSON(workflow)
}
// UpdateWorkflow updates an existing workflow definition
func (api *WorkflowAPI) UpdateWorkflow(c *fiber.Ctx) error {
id := c.Params("id")
if id == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Workflow ID is required",
})
}
var definition WorkflowDefinition
if err := c.BodyParser(&definition); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request body",
})
}
// Ensure ID matches
definition.ID = id
definition.UpdatedAt = time.Now()
if err := api.enhancedDAG.RegisterWorkflow(c.Context(), &definition); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": err.Error(),
})
}
return c.JSON(definition)
}
// DeleteWorkflow deletes a workflow definition
func (api *WorkflowAPI) DeleteWorkflow(c *fiber.Ctx) error {
id := c.Params("id")
if id == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Workflow ID is required",
})
}
// For now, we'll just return success
// In a real implementation, you'd remove it from the registry
return c.JSON(fiber.Map{
"message": "Workflow deleted successfully",
"id": id,
})
}
// ExecuteWorkflow starts execution of a workflow
func (api *WorkflowAPI) ExecuteWorkflow(c *fiber.Ctx) error {
id := c.Params("id")
if id == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Workflow ID is required",
})
}
var input map[string]interface{}
if err := c.BodyParser(&input); err != nil {
input = make(map[string]interface{})
}
execution, err := api.enhancedDAG.ExecuteWorkflow(c.Context(), id, input)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": err.Error(),
})
}
return c.Status(fiber.StatusCreated).JSON(execution)
}
// ListWorkflowExecutions lists executions for a specific workflow
func (api *WorkflowAPI) ListWorkflowExecutions(c *fiber.Ctx) error {
workflowID := c.Params("id")
if workflowID == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Workflow ID is required",
})
}
activeExecutions := api.enhancedDAG.ListActiveExecutions()
// Filter by workflow ID
filtered := make([]*WorkflowExecution, 0)
for _, exec := range activeExecutions {
if exec.WorkflowID == workflowID {
filtered = append(filtered, exec)
}
}
return c.JSON(fiber.Map{
"executions": filtered,
"total": len(filtered),
})
}
// ListAllExecutions lists all workflow executions
func (api *WorkflowAPI) ListAllExecutions(c *fiber.Ctx) error {
activeExecutions := api.enhancedDAG.ListActiveExecutions()
return c.JSON(fiber.Map{
"executions": activeExecutions,
"total": len(activeExecutions),
})
}
// GetExecution retrieves a specific workflow execution
func (api *WorkflowAPI) GetExecution(c *fiber.Ctx) error {
executionID := c.Params("executionId")
if executionID == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Execution ID is required",
})
}
execution, err := api.enhancedDAG.GetExecution(executionID)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"error": err.Error(),
})
}
return c.JSON(execution)
}
// CancelExecution cancels a running workflow execution
func (api *WorkflowAPI) CancelExecution(c *fiber.Ctx) error {
executionID := c.Params("executionId")
if executionID == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Execution ID is required",
})
}
if err := api.enhancedDAG.CancelExecution(executionID); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": err.Error(),
})
}
return c.JSON(fiber.Map{
"message": "Execution cancelled successfully",
"id": executionID,
})
}
// HealthCheck provides health status of the workflow system
func (api *WorkflowAPI) HealthCheck(c *fiber.Ctx) error {
workflows := api.enhancedDAG.ListWorkflows()
activeExecutions := api.enhancedDAG.ListActiveExecutions()
return c.JSON(fiber.Map{
"status": "healthy",
"workflows": len(workflows),
"active_executions": len(activeExecutions),
"timestamp": time.Now(),
})
}
// GetMetrics provides system metrics
func (api *WorkflowAPI) GetMetrics(c *fiber.Ctx) error {
workflows := api.enhancedDAG.ListWorkflows()
activeExecutions := api.enhancedDAG.ListActiveExecutions()
// Basic metrics
metrics := fiber.Map{
"workflows": fiber.Map{
"total": len(workflows),
"by_status": make(map[string]int),
},
"executions": fiber.Map{
"active": len(activeExecutions),
"by_status": make(map[string]int),
},
}
// Count workflows by status
statusCounts := metrics["workflows"].(fiber.Map)["by_status"].(map[string]int)
for _, w := range workflows {
statusCounts[string(w.Status)]++
}
// Count executions by status
execStatusCounts := metrics["executions"].(fiber.Map)["by_status"].(map[string]int)
for _, e := range activeExecutions {
execStatusCounts[string(e.Status)]++
}
return c.JSON(metrics)
}
// Helper method to extend existing DAG API with workflow features
func (tm *DAG) RegisterWorkflowAPI(app *fiber.App) error {
// Create enhanced DAG if not already created
enhanced, err := NewEnhancedDAG(tm.name, tm.key, nil)
if err != nil {
return err
}
// Copy existing DAG state to enhanced DAG
enhanced.DAG = tm
// Create and register workflow API
workflowAPI := NewWorkflowAPI(enhanced)
workflowAPI.RegisterWorkflowRoutes(app)
return nil
}