veza/veza-backend-api/internal/core/admin/handler.go

340 lines
10 KiB
Go
Raw Normal View History

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"})
}