veza/veza-backend-api/internal/handlers/social.go
senke cc2ebae4dc feat: Visual masterpiece - true light mode & premium UI
🎨 **True Light/Dark Mode**
- Implemented proper light mode with inverted color scheme
- Smooth theme transitions (0.3s ease)
- Light mode colors: white backgrounds, dark text, vibrant accents
- System theme detection with proper class application

🌈 **Enhanced Theme System**
- 4 color themes work in both light and dark modes
- Cyber (cyan/magenta), Ocean (blue/teal), Forest (green/lime), Sunset (orange/purple)
- Theme-specific glassmorphism effects
- Proper contrast in light mode

 **Premium Animations**
- Float, glow-pulse, slide-in, scale-in, rotate-in animations
- Smooth page transitions
- Hover effects with depth (lift, glow, scale)
- Micro-interactions on all interactive elements

🎯 **Visual Polish**
- Enhanced glassmorphism for light/dark modes
- Custom scrollbar with theme colors
- Beautiful text selection
- Focus indicators for accessibility
- Premium utility classes

🔧 **Technical Improvements**
- Updated UIStore to properly apply light/dark classes
- Added data-theme attribute for CSS targeting
- Smooth scroll behavior
- Optimized transitions

The app is now a visual masterpiece with perfect light/dark mode support!
2026-01-11 02:32:21 +01:00

199 lines
6.3 KiB
Go

package handlers
import (
"net/http"
"strconv"
"veza-backend-api/internal/core/social"
"veza-backend-api/internal/utils"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"go.uber.org/zap"
)
// SocialHandler gère les opérations sociales
type SocialHandler struct {
service social.SocialService
commonHandler *CommonHandler
}
// NewSocialHandler crée une nouvelle instance de SocialHandler
func NewSocialHandler(service social.SocialService, logger *zap.Logger) *SocialHandler {
return &SocialHandler{
service: service,
commonHandler: NewCommonHandler(logger),
}
}
// NewSocialHandlerWithInterface crée une nouvelle instance de SocialHandler avec une interface (pour les tests)
func NewSocialHandlerWithInterface(service social.SocialService, logger *zap.Logger) *SocialHandler {
return &SocialHandler{
service: service,
commonHandler: NewCommonHandler(logger),
}
}
// CreatePostRequest DTO pour la création de post
// GO-013: Validation améliorée avec tags go-validator
// MOD-P1-001: Ajout tags validate pour validation systématique
type CreatePostRequest struct {
Content string `json:"content" binding:"required,min=1,max=5000" validate:"required,min=1,max=5000"`
Attachments map[string]string `json:"attachments" validate:"omitempty"` // track_id, playlist_id (UUID strings)
}
// CreatePost crée un post
// GO-013: Utilise validator centralisé pour validation améliorée
// P0: JSON Hardening - Utilise BindAndValidateJSON pour une gestion robuste des erreurs
func (h *SocialHandler) CreatePost(c *gin.Context) {
userID, ok := GetUserIDUUID(c)
if !ok {
return // Erreur déjà envoyée par GetUserIDUUID
}
var req CreatePostRequest
if appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil {
RespondWithAppError(c, appErr)
return
}
// BE-SEC-009: Sanitize user inputs to prevent XSS and injection attacks
req.Content = utils.SanitizeText(req.Content, 5000)
attachments := make(map[string]uuid.UUID)
for k, v := range req.Attachments {
if id, err := uuid.Parse(v); err == nil {
attachments[k] = id
}
}
post, err := h.service.CreatePost(c.Request.Context(), userID, req.Content, attachments)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create post"})
return
}
RespondSuccess(c, http.StatusCreated, post)
}
// ToggleLikeRequest DTO pour liker
// GO-013: Validation améliorée avec tags go-validator
// MOD-P1-001: Ajout tags validate pour validation systématique
type ToggleLikeRequest struct {
TargetID string `json:"target_id" binding:"required,uuid" validate:"required,uuid"`
TargetType string `json:"target_type" binding:"required,oneof=post track playlist" validate:"required,oneof=post track playlist"`
}
// ToggleLike like ou unlike un objet
// GO-013: Utilise validator centralisé pour validation améliorée
// P0: JSON Hardening - Utilise BindAndValidateJSON pour une gestion robuste des erreurs
func (h *SocialHandler) ToggleLike(c *gin.Context) {
userID, ok := GetUserIDUUID(c)
if !ok {
return // Erreur déjà envoyée par GetUserIDUUID
}
var req ToggleLikeRequest
if appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil {
RespondWithAppError(c, appErr)
return
}
// UUID validation déjà fait par binding tag, mais on garde le parse pour compatibilité
targetID, err := uuid.Parse(req.TargetID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid target_id format"})
return
}
liked, err := h.service.ToggleLike(c.Request.Context(), userID, targetID, req.TargetType)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to toggle like"})
return
}
RespondSuccess(c, http.StatusOK, gin.H{"liked": liked})
}
// AddCommentRequest DTO pour commenter
// GO-013: Validation améliorée avec tags go-validator
// MOD-P1-001: Ajout tags validate pour validation systématique
type AddCommentRequest struct {
TargetID string `json:"target_id" binding:"required,uuid" validate:"required,uuid"`
TargetType string `json:"target_type" binding:"required,oneof=post track playlist" validate:"required,oneof=post track playlist"`
Content string `json:"content" binding:"required,min=1,max=2000" validate:"required,min=1,max=2000"`
}
// AddComment ajoute un commentaire
// GO-013: Utilise validator centralisé pour validation améliorée
// P0: JSON Hardening - Utilise BindAndValidateJSON pour une gestion robuste des erreurs
func (h *SocialHandler) AddComment(c *gin.Context) {
userID, ok := GetUserIDUUID(c)
if !ok {
return // Erreur déjà envoyée par GetUserIDUUID
}
var req AddCommentRequest
if appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil {
RespondWithAppError(c, appErr)
return
}
// UUID validation déjà fait par binding tag, mais on garde le parse pour compatibilité
targetID, err := uuid.Parse(req.TargetID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid target_id format"})
return
}
comment, err := h.service.AddComment(c.Request.Context(), userID, targetID, req.TargetType, req.Content)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to add comment"})
return
}
RespondSuccess(c, http.StatusCreated, comment)
}
// GetFeed récupère le feed global
func (h *SocialHandler) GetFeed(c *gin.Context) {
feed, err := h.service.GetGlobalFeed(c.Request.Context(), 20, 0)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get feed"})
return
}
RespondSuccess(c, http.StatusOK, feed)
}
// GetPostsByUser récupère les posts d'un utilisateur spécifique
func (h *SocialHandler) GetPostsByUser(c *gin.Context) {
userIDStr := c.Param("user_id")
userID, err := uuid.Parse(userIDStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
return
}
pageParam := c.DefaultQuery("page", "1")
limitParam := c.DefaultQuery("limit", "20")
page, err := strconv.Atoi(pageParam)
if err != nil || page < 1 {
page = 1
}
limit, err := strconv.Atoi(limitParam)
if err != nil || limit < 1 {
limit = 20
}
offset := (page - 1) * limit
posts, err := h.service.GetPostsByUser(c.Request.Context(), userID, limit, offset)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get posts"})
return
}
RespondSuccess(c, http.StatusOK, posts)
}