339 lines
10 KiB
Go
339 lines
10 KiB
Go
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"})
|
|
}
|