package handlers import ( "net/http" "strconv" "github.com/gin-gonic/gin" "go.uber.org/zap" "github.com/kms/api-key-service/internal/authorization" "github.com/kms/api-key-service/internal/domain" "github.com/kms/api-key-service/internal/errors" "github.com/kms/api-key-service/internal/services" "github.com/kms/api-key-service/internal/validation" ) // ApplicationHandler handles application-related HTTP requests type ApplicationHandler struct { appService services.ApplicationService authService services.AuthenticationService authzService *authorization.AuthorizationService validator *validation.Validator errorHandler *errors.ErrorHandler logger *zap.Logger } // NewApplicationHandler creates a new application handler func NewApplicationHandler( appService services.ApplicationService, authService services.AuthenticationService, logger *zap.Logger, ) *ApplicationHandler { return &ApplicationHandler{ appService: appService, authService: authService, authzService: authorization.NewAuthorizationService(logger), validator: validation.NewValidator(logger), errorHandler: errors.NewErrorHandler(logger), logger: logger, } } // Create handles POST /applications func (h *ApplicationHandler) Create(c *gin.Context) { var req domain.CreateApplicationRequest if err := c.ShouldBindJSON(&req); err != nil { h.errorHandler.HandleValidationError(c, "request_body", "Invalid application request format") return } // Get user ID from authenticated context userID := h.getUserIDFromContext(c) if userID == "" { h.errorHandler.HandleAuthenticationError(c, errors.NewUnauthorizedError("User authentication required")) return } // Validate input validationErrors := h.validator.ValidateApplicationRequest(req.AppID, req.AppLink, req.CallbackURL, []string{}) if len(validationErrors) > 0 { h.logger.Warn("Application validation failed", zap.String("user_id", userID), zap.Any("errors", validationErrors)) h.errorHandler.HandleValidationError(c, "validation", "Invalid application data") return } // Check authorization for creating applications authCtx := &authorization.AuthorizationContext{ UserID: userID, ResourceType: authorization.ResourceTypeApplication, Action: authorization.ActionCreate, } if err := h.authzService.AuthorizeResourceAccess(c.Request.Context(), authCtx); err != nil { h.errorHandler.HandleAuthorizationError(c, "application creation") return } // Create the application app, err := h.appService.Create(c.Request.Context(), &req, userID) if err != nil { h.errorHandler.HandleInternalError(c, err) return } h.logger.Info("Application created successfully", zap.String("app_id", app.AppID), zap.String("user_id", userID)) c.JSON(http.StatusCreated, app) } // getUserIDFromContext extracts user ID from Gin context func (h *ApplicationHandler) getUserIDFromContext(c *gin.Context) string { // Try to get from Gin context first (set by middleware) if userID, exists := c.Get("user_id"); exists { if id, ok := userID.(string); ok { return id } } // Fallback to header (for compatibility) userEmail := c.GetHeader("X-User-Email") if userEmail != "" { return userEmail } return "" } // GetByID handles GET /applications/:id func (h *ApplicationHandler) GetByID(c *gin.Context) { appID := c.Param("id") // Get user ID from context userID := h.getUserIDFromContext(c) if userID == "" { h.errorHandler.HandleAuthenticationError(c, errors.NewUnauthorizedError("User authentication required")) return } // Validate app ID if result := h.validator.ValidateAppID(appID); !result.Valid { h.errorHandler.HandleValidationError(c, "app_id", "Invalid application ID") return } // Get the application first app, err := h.appService.GetByID(c.Request.Context(), appID) if err != nil { h.logger.Error("Failed to get application", zap.Error(err), zap.String("app_id", appID)) h.errorHandler.HandleError(c, err, "Application not found") return } // Check authorization for reading this application if err := h.authzService.AuthorizeApplicationOwnership(userID, app); err != nil { h.errorHandler.HandleAuthorizationError(c, "application access") return } c.JSON(http.StatusOK, app) } // List handles GET /applications func (h *ApplicationHandler) List(c *gin.Context) { // Parse pagination parameters limit := 50 offset := 0 if l := c.Query("limit"); l != "" { if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 { limit = parsed } } if o := c.Query("offset"); o != "" { if parsed, err := strconv.Atoi(o); err == nil && parsed >= 0 { offset = parsed } } apps, err := h.appService.List(c.Request.Context(), limit, offset) if err != nil { h.logger.Error("Failed to list applications", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{ "error": "Internal Server Error", "message": "Failed to list applications", }) return } c.JSON(http.StatusOK, gin.H{ "data": apps, "limit": limit, "offset": offset, "count": len(apps), }) } // Update handles PUT /applications/:id func (h *ApplicationHandler) Update(c *gin.Context) { appID := c.Param("id") if appID == "" { c.JSON(http.StatusBadRequest, gin.H{ "error": "Bad Request", "message": "Application ID is required", }) return } var req domain.UpdateApplicationRequest if err := c.ShouldBindJSON(&req); err != nil { h.logger.Warn("Invalid request body", zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{ "error": "Bad Request", "message": "Invalid request body: " + err.Error(), }) return } // Get user ID from context userID, exists := c.Get("user_id") if !exists { h.logger.Error("User ID not found in context") c.JSON(http.StatusInternalServerError, gin.H{ "error": "Internal Server Error", "message": "Authentication context not found", }) return } app, err := h.appService.Update(c.Request.Context(), appID, &req, userID.(string)) if err != nil { h.logger.Error("Failed to update application", zap.Error(err), zap.String("app_id", appID)) c.JSON(http.StatusInternalServerError, gin.H{ "error": "Internal Server Error", "message": "Failed to update application", }) return } h.logger.Info("Application updated", zap.String("app_id", appID)) c.JSON(http.StatusOK, app) } // Delete handles DELETE /applications/:id func (h *ApplicationHandler) Delete(c *gin.Context) { appID := c.Param("id") if appID == "" { c.JSON(http.StatusBadRequest, gin.H{ "error": "Bad Request", "message": "Application ID is required", }) return } // Get user ID from context userID, exists := c.Get("user_id") if !exists { h.logger.Error("User ID not found in context") c.JSON(http.StatusInternalServerError, gin.H{ "error": "Internal Server Error", "message": "Authentication context not found", }) return } err := h.appService.Delete(c.Request.Context(), appID, userID.(string)) if err != nil { h.logger.Error("Failed to delete application", zap.Error(err), zap.String("app_id", appID)) c.JSON(http.StatusInternalServerError, gin.H{ "error": "Internal Server Error", "message": "Failed to delete application", }) return } h.logger.Info("Application deleted", zap.String("app_id", appID)) c.JSON(http.StatusNoContent, nil) }