package admin import ( "net/http" "strconv" "strings" apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/handlers" "veza-backend-api/internal/services" "github.com/gin-gonic/gin" "github.com/google/uuid" "go.uber.org/zap" ) // PlatformAdminHandler handles admin platform HTTP requests (F421-F435) type PlatformAdminHandler struct { service *services.AdminPlatformService logger *zap.Logger } // NewPlatformAdminHandler creates a new platform admin handler func NewPlatformAdminHandler(service *services.AdminPlatformService, logger *zap.Logger) *PlatformAdminHandler { if logger == nil { logger = zap.NewNop() } return &PlatformAdminHandler{service: service, logger: logger} } // getAdminID extracts admin user ID from context func (h *PlatformAdminHandler) getAdminID(c *gin.Context) (uuid.UUID, bool) { userID, ok := handlers.GetUserIDUUID(c) if !ok || userID == uuid.Nil { handlers.RespondWithAppError(c, apperrors.New(apperrors.ErrCodeUnauthorized, "authentication required")) return uuid.Nil, false } return userID, true } // --- F421: Platform Dashboard --- // GetPlatformMetrics handles GET /api/v1/admin/platform/metrics (F421) func (h *PlatformAdminHandler) GetPlatformMetrics(c *gin.Context) { metrics, err := h.service.GetPlatformMetrics(c.Request.Context()) if err != nil { handlers.RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "failed to get metrics", err)) return } handlers.RespondSuccess(c, http.StatusOK, gin.H{ "metrics": metrics, }) } // --- F422: User Management --- // SearchUsers handles GET /api/v1/admin/platform/users (F422) func (h *PlatformAdminHandler) SearchUsers(c *gin.Context) { limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20")) offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0")) var isBanned *bool if b := c.Query("is_banned"); b != "" { v := b == "true" isBanned = &v } params := services.AdminUserSearchParams{ Query: c.Query("q"), Role: c.Query("role"), IsBanned: isBanned, Limit: limit, Offset: offset, SortBy: c.DefaultQuery("sort_by", "created_at"), } users, total, err := h.service.SearchUsers(c.Request.Context(), params) if err != nil { handlers.RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "failed to search users", err)) return } handlers.RespondSuccess(c, http.StatusOK, gin.H{ "users": users, "pagination": gin.H{ "total": total, "limit": limit, "offset": offset, }, }) } // GetUserDetail handles GET /api/v1/admin/platform/users/:userId (F422) func (h *PlatformAdminHandler) GetUserDetail(c *gin.Context) { userIDStr := c.Param("userId") userID, err := uuid.Parse(userIDStr) if err != nil { handlers.RespondWithAppError(c, apperrors.NewValidationError("invalid user ID")) return } user, err := h.service.GetUserDetail(c.Request.Context(), userID) if err != nil { if strings.Contains(err.Error(), "not found") { handlers.RespondWithAppError(c, apperrors.NewNotFoundError("user")) return } handlers.RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "failed to get user", err)) return } handlers.RespondSuccess(c, http.StatusOK, gin.H{ "user": user, }) } // UpdateUserRole handles PUT /api/v1/admin/platform/users/:userId/role (F422) func (h *PlatformAdminHandler) UpdateUserRole(c *gin.Context) { userIDStr := c.Param("userId") userID, err := uuid.Parse(userIDStr) if err != nil { handlers.RespondWithAppError(c, apperrors.NewValidationError("invalid user ID")) return } var req struct { Role string `json:"role" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { handlers.RespondWithAppError(c, apperrors.NewValidationError("role is required")) return } if err := h.service.UpdateUserRole(c.Request.Context(), userID, req.Role); err != nil { if strings.Contains(err.Error(), "invalid role") { handlers.RespondWithAppError(c, apperrors.NewValidationError(err.Error())) return } if strings.Contains(err.Error(), "not found") { handlers.RespondWithAppError(c, apperrors.NewNotFoundError("user")) return } handlers.RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "failed to update role", err)) return } handlers.RespondSuccess(c, http.StatusOK, gin.H{"status": "role_updated"}) } // SuspendUser handles POST /api/v1/admin/platform/users/:userId/suspend (F422) func (h *PlatformAdminHandler) SuspendUser(c *gin.Context) { adminID, ok := h.getAdminID(c) if !ok { return } userIDStr := c.Param("userId") userID, err := uuid.Parse(userIDStr) if err != nil { handlers.RespondWithAppError(c, apperrors.NewValidationError("invalid user ID")) return } var req struct { Reason string `json:"reason" binding:"required"` DurationDays *int `json:"duration_days"` } if err := c.ShouldBindJSON(&req); err != nil { handlers.RespondWithAppError(c, apperrors.NewValidationError("reason is required")) return } if err := h.service.SuspendUser(c.Request.Context(), userID, adminID, req.Reason, req.DurationDays); err != nil { handlers.RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "failed to suspend user", err)) return } handlers.RespondSuccess(c, http.StatusOK, gin.H{"status": "suspended"}) } // UnsuspendUser handles POST /api/v1/admin/platform/users/:userId/unsuspend (F422) func (h *PlatformAdminHandler) UnsuspendUser(c *gin.Context) { adminID, ok := h.getAdminID(c) if !ok { return } userIDStr := c.Param("userId") userID, err := uuid.Parse(userIDStr) if err != nil { handlers.RespondWithAppError(c, apperrors.NewValidationError("invalid user ID")) return } if err := h.service.UnsuspendUser(c.Request.Context(), userID, adminID); err != nil { handlers.RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "failed to unsuspend user", err)) return } handlers.RespondSuccess(c, http.StatusOK, gin.H{"status": "unsuspended"}) } // --- F423: Content Management --- // SearchContent handles GET /api/v1/admin/platform/content (F423) func (h *PlatformAdminHandler) SearchContent(c *gin.Context) { limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20")) offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0")) items, total, err := h.service.SearchContent(c.Request.Context(), c.DefaultQuery("type", "track"), c.Query("q"), limit, offset) if err != nil { handlers.RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "failed to search content", err)) return } handlers.RespondSuccess(c, http.StatusOK, gin.H{ "content": items, "pagination": gin.H{ "total": total, "limit": limit, "offset": offset, }, }) } // HideContent handles POST /api/v1/admin/platform/content/:id/hide (F423) func (h *PlatformAdminHandler) HideContent(c *gin.Context) { adminID, ok := h.getAdminID(c) if !ok { return } contentIDStr := c.Param("id") contentID, err := uuid.Parse(contentIDStr) if err != nil { handlers.RespondWithAppError(c, apperrors.NewValidationError("invalid content ID")) return } var req struct { ContentType string `json:"content_type" binding:"required"` Reason string `json:"reason"` } if err := c.ShouldBindJSON(&req); err != nil { handlers.RespondWithAppError(c, apperrors.NewValidationError("content_type is required")) return } if err := h.service.HideContent(c.Request.Context(), adminID, req.ContentType, contentID, req.Reason); err != nil { if strings.Contains(err.Error(), "invalid content type") { handlers.RespondWithAppError(c, apperrors.NewValidationError(err.Error())) return } handlers.RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "failed to hide content", err)) return } handlers.RespondSuccess(c, http.StatusOK, gin.H{"status": "hidden"}) } // RestoreContent handles POST /api/v1/admin/platform/content/:id/restore (F423) func (h *PlatformAdminHandler) RestoreContent(c *gin.Context) { adminID, ok := h.getAdminID(c) if !ok { return } contentIDStr := c.Param("id") contentID, err := uuid.Parse(contentIDStr) if err != nil { handlers.RespondWithAppError(c, apperrors.NewValidationError("invalid content ID")) return } var req struct { ContentType string `json:"content_type" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { handlers.RespondWithAppError(c, apperrors.NewValidationError("content_type is required")) return } if err := h.service.RestoreContent(c.Request.Context(), adminID, req.ContentType, contentID); err != nil { handlers.RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "failed to restore content", err)) return } handlers.RespondSuccess(c, http.StatusOK, gin.H{"status": "restored"}) } // --- F424: Payment Management --- // GetPaymentOverview handles GET /api/v1/admin/platform/payments (F424) func (h *PlatformAdminHandler) GetPaymentOverview(c *gin.Context) { overview, err := h.service.GetPaymentOverview(c.Request.Context()) if err != nil { handlers.RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "failed to get payment overview", err)) return } handlers.RespondSuccess(c, http.StatusOK, gin.H{ "payments": overview, }) } // RefundOrder handles POST /api/v1/admin/platform/orders/:id/refund (F424) func (h *PlatformAdminHandler) RefundOrder(c *gin.Context) { adminID, ok := h.getAdminID(c) if !ok { return } orderIDStr := c.Param("id") orderID, err := uuid.Parse(orderIDStr) if err != nil { handlers.RespondWithAppError(c, apperrors.NewValidationError("invalid order ID")) return } var req struct { Reason string `json:"reason" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { handlers.RespondWithAppError(c, apperrors.NewValidationError("reason is required")) return } if err := h.service.RefundOrder(c.Request.Context(), adminID, orderID, req.Reason); err != nil { if strings.Contains(err.Error(), "not found") || strings.Contains(err.Error(), "not eligible") { handlers.RespondWithAppError(c, apperrors.NewValidationError(err.Error())) return } handlers.RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "failed to refund order", err)) return } handlers.RespondSuccess(c, http.StatusOK, gin.H{"status": "refunded"}) }