package handlers import ( "net/http" "strconv" "github.com/gin-gonic/gin" "github.com/google/uuid" "go.uber.org/zap" "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" ) // TokenHandler handles token-related HTTP requests type TokenHandler struct { tokenService services.TokenService authService services.AuthenticationService validator *validation.Validator errorHandler *errors.ErrorHandler logger *zap.Logger } // NewTokenHandler creates a new token handler func NewTokenHandler( tokenService services.TokenService, authService services.AuthenticationService, logger *zap.Logger, ) *TokenHandler { return &TokenHandler{ tokenService: tokenService, authService: authService, validator: validation.NewValidator(logger), errorHandler: errors.NewErrorHandler(logger), logger: logger, } } // Create handles POST /applications/:id/tokens func (h *TokenHandler) Create(c *gin.Context) { // Validate application ID parameter appID := c.Param("id") if appID == "" { h.errorHandler.HandleValidationError(c, "id", "Application ID is required") return } // Bind and validate JSON request var req domain.CreateStaticTokenRequest if err := c.ShouldBindJSON(&req); err != nil { h.logger.Warn("Invalid request body", zap.Error(err)) h.errorHandler.HandleValidationError(c, "request_body", "Invalid request body format") return } // Set app ID from URL parameter req.AppID = appID // Basic validation - the service layer will do more comprehensive validation if req.AppID == "" { h.errorHandler.HandleValidationError(c, "app_id", "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") h.errorHandler.HandleAuthenticationError(c, errors.NewAuthenticationError("Authentication context not found")) return } userIDStr, ok := userID.(string) if !ok { h.logger.Error("Invalid user ID type in context", zap.Any("user_id", userID)) h.errorHandler.HandleInternalError(c, errors.NewInternalError("Invalid authentication context")) return } // Create the token token, err := h.tokenService.CreateStaticToken(c.Request.Context(), &req, userIDStr) if err != nil { h.logger.Error("Failed to create token", zap.Error(err), zap.String("app_id", appID), zap.String("user_id", userIDStr)) // Handle different types of errors appropriately if errors.IsNotFound(err) { h.errorHandler.HandleError(c, err, "Application not found") } else if errors.IsValidationError(err) { h.errorHandler.HandleValidationError(c, "token", "Token creation validation failed") } else if errors.IsAuthorizationError(err) { h.errorHandler.HandleAuthorizationError(c, "token_creation") } else { h.errorHandler.HandleInternalError(c, err) } return } h.logger.Info("Token created successfully", zap.String("token_id", token.ID.String()), zap.String("app_id", appID), zap.String("user_id", userIDStr)) c.JSON(http.StatusCreated, token) } // ListByApp handles GET /applications/:id/tokens func (h *TokenHandler) ListByApp(c *gin.Context) { // Validate application ID parameter appID := c.Param("id") if appID == "" { h.errorHandler.HandleValidationError(c, "id", "Application ID is required") return } // Parse and validate pagination parameters limit := 50 offset := 0 if l := c.Query("limit"); l != "" { if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 && parsed <= 1000 { limit = parsed } else if parsed <= 0 || parsed > 1000 { h.errorHandler.HandleValidationError(c, "limit", "Limit must be between 1 and 1000") return } } if o := c.Query("offset"); o != "" { if parsed, err := strconv.Atoi(o); err == nil && parsed >= 0 { offset = parsed } else if parsed < 0 { h.errorHandler.HandleValidationError(c, "offset", "Offset must be non-negative") return } } // List tokens tokens, err := h.tokenService.ListByApp(c.Request.Context(), appID, limit, offset) if err != nil { h.logger.Error("Failed to list tokens", zap.Error(err), zap.String("app_id", appID), zap.Int("limit", limit), zap.Int("offset", offset)) // Handle different types of errors appropriately if errors.IsNotFound(err) { h.errorHandler.HandleNotFoundError(c, "application", "Application not found") } else if errors.IsAuthorizationError(err) { h.errorHandler.HandleAuthorizationError(c, "token_list") } else { h.errorHandler.HandleInternalError(c, err) } return } h.logger.Debug("Tokens listed successfully", zap.String("app_id", appID), zap.Int("token_count", len(tokens)), zap.Int("limit", limit), zap.Int("offset", offset)) c.JSON(http.StatusOK, gin.H{ "data": tokens, "limit": limit, "offset": offset, "count": len(tokens), }) } // Delete handles DELETE /tokens/:id func (h *TokenHandler) Delete(c *gin.Context) { // Validate token ID parameter tokenIDStr := c.Param("id") if tokenIDStr == "" { h.errorHandler.HandleValidationError(c, "id", "Token ID is required") return } tokenID, err := uuid.Parse(tokenIDStr) if err != nil { h.logger.Warn("Invalid token ID format", zap.String("token_id", tokenIDStr), zap.Error(err)) h.errorHandler.HandleValidationError(c, "id", "Invalid token ID format") return } // Get user ID from context userID, exists := c.Get("user_id") if !exists { h.logger.Error("User ID not found in context") h.errorHandler.HandleAuthenticationError(c, errors.NewAuthenticationError("Authentication context not found")) return } userIDStr, ok := userID.(string) if !ok { h.logger.Error("Invalid user ID type in context", zap.Any("user_id", userID)) h.errorHandler.HandleInternalError(c, errors.NewInternalError("Invalid authentication context")) return } // Delete the token err = h.tokenService.Delete(c.Request.Context(), tokenID, userIDStr) if err != nil { h.logger.Error("Failed to delete token", zap.Error(err), zap.String("token_id", tokenID.String()), zap.String("user_id", userIDStr)) // Handle different types of errors appropriately if errors.IsNotFound(err) { h.errorHandler.HandleNotFoundError(c, "token", "Token not found") } else if errors.IsAuthorizationError(err) { h.errorHandler.HandleAuthorizationError(c, "token_deletion") } else { h.errorHandler.HandleInternalError(c, err) } return } h.logger.Info("Token deleted successfully", zap.String("token_id", tokenID.String()), zap.String("user_id", userIDStr)) c.JSON(http.StatusNoContent, nil) }