263 lines
7.2 KiB
Go
263 lines
7.2 KiB
Go
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)
|
|
}
|