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 FunctionHandler struct { functionService services.FunctionService authService services.AuthService logger *zap.Logger } func NewFunctionHandler(functionService services.FunctionService, authService services.AuthService, logger *zap.Logger) *FunctionHandler { return &FunctionHandler{ functionService: functionService, authService: authService, logger: logger, } } func (h *FunctionHandler) Create(c *gin.Context) { var req domain.CreateFunctionRequest if err := c.ShouldBindJSON(&req); err != nil { h.logger.Warn("Invalid create function request", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request format"}) return } // Auto-select image based on runtime if not provided or empty if req.Image == "" { if runtimeConfig, exists := domain.GetRuntimeConfig(req.Runtime); exists && runtimeConfig.Image != "" { req.Image = runtimeConfig.Image } } 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.write") { c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"}) return } function, err := h.functionService.Create(c.Request.Context(), &req, authCtx.UserID) if err != nil { h.logger.Error("Failed to create function", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } h.logger.Info("Function created successfully", zap.String("function_id", function.ID.String()), zap.String("name", function.Name), zap.String("user_id", authCtx.UserID)) c.JSON(http.StatusCreated, function) } func (h *FunctionHandler) GetByID(c *gin.Context) { idStr := c.Param("id") id, err := uuid.Parse(idStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid function ID"}) return } if !h.authService.HasPermission(c.Request.Context(), "faas.read") { c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"}) return } function, err := h.functionService.GetByID(c.Request.Context(), id) if err != nil { h.logger.Error("Failed to get function", zap.String("id", idStr), zap.Error(err)) c.JSON(http.StatusNotFound, gin.H{"error": "Function not found"}) return } c.JSON(http.StatusOK, function) } func (h *FunctionHandler) Update(c *gin.Context) { idStr := c.Param("id") id, err := uuid.Parse(idStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid function ID"}) return } var req domain.UpdateFunctionRequest if err := c.ShouldBindJSON(&req); err != nil { h.logger.Warn("Invalid update function request", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request format"}) 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.write") { c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"}) return } function, err := h.functionService.Update(c.Request.Context(), id, &req, authCtx.UserID) if err != nil { h.logger.Error("Failed to update function", zap.String("id", idStr), zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } h.logger.Info("Function updated successfully", zap.String("function_id", id.String()), zap.String("user_id", authCtx.UserID)) c.JSON(http.StatusOK, function) } func (h *FunctionHandler) Delete(c *gin.Context) { idStr := c.Param("id") id, err := uuid.Parse(idStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid function 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.delete") { c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"}) return } if err := h.functionService.Delete(c.Request.Context(), id, authCtx.UserID); err != nil { h.logger.Error("Failed to delete function", zap.String("id", idStr), zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } h.logger.Info("Function deleted successfully", zap.String("function_id", id.String()), zap.String("user_id", authCtx.UserID)) c.JSON(http.StatusOK, gin.H{"message": "Function deleted successfully"}) } func (h *FunctionHandler) List(c *gin.Context) { if !h.authService.HasPermission(c.Request.Context(), "faas.read") { c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"}) return } appID := c.Query("app_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 } functions, err := h.functionService.List(c.Request.Context(), appID, limit, offset) if err != nil { h.logger.Error("Failed to list functions", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "functions": functions, "limit": limit, "offset": offset, }) } func (h *FunctionHandler) Deploy(c *gin.Context) { idStr := c.Param("id") id, err := uuid.Parse(idStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid function ID"}) return } var req domain.DeployFunctionRequest if err := c.ShouldBindJSON(&req); err != nil { // Allow empty body for deploy req = domain.DeployFunctionRequest{ FunctionID: id, Force: false, } } req.FunctionID = id 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.deploy") { c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"}) return } response, err := h.functionService.Deploy(c.Request.Context(), id, &req, authCtx.UserID) if err != nil { h.logger.Error("Failed to deploy function", zap.String("id", idStr), zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } h.logger.Info("Function deployed successfully", zap.String("function_id", id.String()), zap.String("user_id", authCtx.UserID)) c.JSON(http.StatusOK, response) }