mirror of
				https://github.com/oarkflow/mq.git
				synced 2025-11-01 05:32:42 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			346 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			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]any
 | |
| 	if err := c.BodyParser(&input); err != nil {
 | |
| 		input = make(map[string]any)
 | |
| 	}
 | |
| 
 | |
| 	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
 | |
| }
 | 
