package handlers import ( "net/http" "strconv" "github.com/gin-gonic/gin" "github.com/google/uuid" "go.uber.org/zap" "github.com/RyanCopley/skybridge/faas/internal/domain" "github.com/RyanCopley/skybridge/faas/internal/services" ) type ExecutionHandler struct { executionService services.ExecutionService authService services.AuthService logger *zap.Logger } func NewExecutionHandler(executionService services.ExecutionService, authService services.AuthService, logger *zap.Logger) *ExecutionHandler { return &ExecutionHandler{ executionService: executionService, authService: authService, logger: logger, } } func (h *ExecutionHandler) Execute(c *gin.Context) { idStr := c.Param("id") functionID, err := uuid.Parse(idStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid function ID"}) return } var req domain.ExecuteFunctionRequest if err := c.ShouldBindJSON(&req); err != nil { h.logger.Warn("Invalid execute function request", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request format"}) return } req.FunctionID = functionID authCtx, err := h.authService.GetAuthContext(c.Request.Context()) if err != nil { h.logger.Error("Failed to get auth context", zap.Error(err)) c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"}) return } if !h.authService.HasPermission(c.Request.Context(), "faas.execute") { c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"}) return } response, err := h.executionService.Execute(c.Request.Context(), &req, authCtx.UserID) if err != nil { h.logger.Error("Failed to execute function", zap.String("function_id", idStr), zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } h.logger.Info("Function execution initiated", zap.String("function_id", functionID.String()), zap.String("execution_id", response.ExecutionID.String()), zap.String("user_id", authCtx.UserID), zap.Bool("async", req.Async)) c.JSON(http.StatusOK, response) } func (h *ExecutionHandler) Invoke(c *gin.Context) { idStr := c.Param("id") functionID, err := uuid.Parse(idStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid function ID"}) return } var req domain.ExecuteFunctionRequest if err := c.ShouldBindJSON(&req); err != nil { // Allow empty body req = domain.ExecuteFunctionRequest{ FunctionID: functionID, Async: true, } } req.FunctionID = functionID req.Async = true // Force async for invoke endpoint authCtx, err := h.authService.GetAuthContext(c.Request.Context()) if err != nil { h.logger.Error("Failed to get auth context", zap.Error(err)) c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"}) return } if !h.authService.HasPermission(c.Request.Context(), "faas.execute") { c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"}) return } response, err := h.executionService.Execute(c.Request.Context(), &req, authCtx.UserID) if err != nil { h.logger.Error("Failed to invoke function", zap.String("function_id", idStr), zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } h.logger.Info("Function invoked successfully", zap.String("function_id", functionID.String()), zap.String("execution_id", response.ExecutionID.String()), zap.String("user_id", authCtx.UserID)) c.JSON(http.StatusAccepted, response) } func (h *ExecutionHandler) GetByID(c *gin.Context) { idStr := c.Param("id") id, err := uuid.Parse(idStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid execution ID"}) return } if !h.authService.HasPermission(c.Request.Context(), "faas.read") { c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"}) return } execution, err := h.executionService.GetByID(c.Request.Context(), id) if err != nil { h.logger.Error("Failed to get execution", zap.String("id", idStr), zap.Error(err)) c.JSON(http.StatusNotFound, gin.H{"error": "Execution not found"}) return } c.JSON(http.StatusOK, execution) } func (h *ExecutionHandler) List(c *gin.Context) { if !h.authService.HasPermission(c.Request.Context(), "faas.read") { c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"}) return } var functionID *uuid.UUID functionIDStr := c.Query("function_id") if functionIDStr != "" { id, err := uuid.Parse(functionIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid function ID"}) return } functionID = &id } limitStr := c.DefaultQuery("limit", "50") offsetStr := c.DefaultQuery("offset", "0") limit, err := strconv.Atoi(limitStr) if err != nil || limit <= 0 { limit = 50 } offset, err := strconv.Atoi(offsetStr) if err != nil || offset < 0 { offset = 0 } executions, err := h.executionService.List(c.Request.Context(), functionID, limit, offset) if err != nil { h.logger.Error("Failed to list executions", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "executions": executions, "limit": limit, "offset": offset, }) } func (h *ExecutionHandler) Cancel(c *gin.Context) { idStr := c.Param("id") id, err := uuid.Parse(idStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid execution ID"}) return } authCtx, err := h.authService.GetAuthContext(c.Request.Context()) if err != nil { h.logger.Error("Failed to get auth context", zap.Error(err)) c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"}) return } if !h.authService.HasPermission(c.Request.Context(), "faas.execute") { c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"}) return } if err := h.executionService.Cancel(c.Request.Context(), id, authCtx.UserID); err != nil { h.logger.Error("Failed to cancel execution", zap.String("id", idStr), zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } h.logger.Info("Execution canceled successfully", zap.String("execution_id", id.String()), zap.String("user_id", authCtx.UserID)) c.JSON(http.StatusOK, gin.H{"message": "Execution canceled successfully"}) } func (h *ExecutionHandler) GetLogs(c *gin.Context) { idStr := c.Param("id") h.logger.Debug("GetLogs endpoint called", zap.String("execution_id", idStr), zap.String("client_ip", c.ClientIP())) id, err := uuid.Parse(idStr) if err != nil { h.logger.Warn("Invalid execution ID provided to GetLogs", zap.String("id", idStr), zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid execution ID"}) return } if !h.authService.HasPermission(c.Request.Context(), "faas.read") { h.logger.Warn("Insufficient permissions for GetLogs", zap.String("execution_id", idStr)) c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"}) return } h.logger.Debug("Calling execution service GetLogs", zap.String("execution_id", idStr)) logs, err := h.executionService.GetLogs(c.Request.Context(), id) if err != nil { h.logger.Error("Failed to get execution logs", zap.String("id", idStr), zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } h.logger.Debug("Successfully retrieved logs from execution service", zap.String("execution_id", idStr), zap.Int("log_count", len(logs))) c.JSON(http.StatusOK, gin.H{ "logs": logs, }) } func (h *ExecutionHandler) GetRunning(c *gin.Context) { if !h.authService.HasPermission(c.Request.Context(), "faas.read") { c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"}) return } executions, err := h.executionService.GetRunningExecutions(c.Request.Context()) if err != nil { h.logger.Error("Failed to get running executions", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "executions": executions, "count": len(executions), }) }