package api import ( "context" "os" "github.com/gin-gonic/gin" "go.uber.org/zap" "veza-backend-api/internal/config" "veza-backend-api/internal/database" "veza-backend-api/internal/handlers" // Single handlers import "veza-backend-api/internal/middleware" "veza-backend-api/internal/repositories" // swaggerFiles "github.com/swaggo/files" // Uncommented // ginSwagger "github.com/swaggo/gin-swagger" // Uncommented // Add missing imports. swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" authcore "veza-backend-api/internal/core/auth" "veza-backend-api/internal/core/marketplace" trackcore "veza-backend-api/internal/core/track" "veza-backend-api/internal/services" "veza-backend-api/internal/validators" "veza-backend-api/internal/workers" // swaggerFiles "github.com/swaggo/files" // ginSwagger "github.com/swaggo/gin-swagger" ) // APIRouter gère la configuration des routes de l'API type APIRouter struct { db *database.Database config *config.Config engine *gin.Engine logger *zap.Logger } // NewAPIRouter crée une nouvelle instance de APIRouter func NewAPIRouter(db *database.Database, cfg *config.Config) *APIRouter { return &APIRouter{ db: db, config: cfg, logger: zap.L(), } } // Setup configure toutes les routes de l'API func (r *APIRouter) Setup(router *gin.Engine) { r.engine = router // Middlewares globaux router.Use(middleware.RequestLogger(r.logger)) // Utilisation du structured logger router.Use(middleware.Metrics()) // Prometheus Metrics router.Use(middleware.SentryRecover(r.logger)) // Sentry error tracking router.Use(middleware.Recovery(r.logger)) // SECURITY: CORS configuration - use config.CORSOrigins strictly (P0-SECURITY) // No fallback to CORSDefault() to avoid wildcard in production if r.config != nil && len(r.config.CORSOrigins) > 0 { router.Use(middleware.CORS(r.config.CORSOrigins)) } else { // If CORSOrigins is empty, log warning but don't use wildcard // This should have been caught by ValidateForEnvironment() in production r.logger.Warn("CORS origins not configured - CORS middleware not applied. This may cause CORS errors in browsers.") } router.Use(middleware.RequestID()) // Rate limiting via config.RateLimiter si disponible, sinon utiliser SimpleRateLimiter if r.config != nil && r.config.RateLimiter != nil { router.Use(r.config.RateLimiter.RateLimitMiddleware()) } else if r.config != nil && r.config.SimpleRateLimiter != nil { router.Use(r.config.SimpleRateLimiter.Middleware()) } // Swagger Documentation router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) // Routes core publiques (health, metrics, upload info) r.setupCorePublicRoutes(router) // Groupe API v1 (nouveau frontend React) v1 := router.Group("/api/v1") { // Routes core protégées (sessions, uploads, audit, admin, conversations) r.setupCoreProtectedRoutes(v1) r.setupAuthRoutes(v1) // Réactivation des routes User et Track pour Phase 1 r.setupUserRoutes(v1) r.setupTrackRoutes(v1) // Réactivation des routes Chat pour Phase 4 r.setupChatRoutes(v1) // Réactivation des routes Playlists pour Phase 5 r.setupPlaylistRoutes(v1) // Réactivation des routes Webhooks r.setupWebhookRoutes(v1) // Marketplace Routes (v1.2.0) r.setupMarketplaceRoutes(v1) } } // Méthodes de configuration des routes par module // setupMarketplaceRoutes configure les routes de la marketplace func (r *APIRouter) setupMarketplaceRoutes(router *gin.RouterGroup) { uploadDir := r.config.UploadDir if uploadDir == "" { uploadDir = "uploads/tracks" } // Storage service (reused from tracks logic) storageService := services.NewTrackStorageService(uploadDir, false, r.logger) // Marketplace service marketService := marketplace.NewService(r.db.GormDB, r.logger, storageService) marketHandler := handlers.NewMarketplaceHandler(marketService, r.logger) group := router.Group("/marketplace") // Public routes group.GET("/products", marketHandler.ListProducts) // Protected routes if r.config.AuthMiddleware != nil { protected := group.Group("") protected.Use(r.config.AuthMiddleware.RequireAuth()) // GO-012: Create product requires creator/premium/admin role createGroup := protected.Group("") createGroup.Use(r.config.AuthMiddleware.RequireContentCreatorRole()) createGroup.POST("/products", marketHandler.CreateProduct) protected.POST("/orders", marketHandler.CreateOrder) protected.GET("/download/:product_id", marketHandler.GetDownloadURL) } } // setupAuthRoutes configure les routes d'authentification avec toutes les dépendances func (r *APIRouter) setupAuthRoutes(router *gin.RouterGroup) { // 1. Instanciation des dépendances emailValidator := validators.NewEmailValidator(r.db.GormDB) passwordValidator := validators.NewPasswordValidator() passwordService := services.NewPasswordService(r.db, r.logger) passwordResetService := services.NewPasswordResetService(r.db, r.logger) jwtService := services.NewJWTService(r.config.JWTSecret) refreshTokenService := services.NewRefreshTokenService(r.db.GormDB) emailVerificationService := services.NewEmailVerificationService(r.db, r.logger) emailService := services.NewEmailService(r.db, r.logger) sessionService := services.NewSessionService(r.db, r.logger) // 2. Service Auth complet authService := authcore.NewAuthService( r.db.GormDB, emailValidator, passwordValidator, passwordService, jwtService, refreshTokenService, emailVerificationService, passwordResetService, emailService, r.config.JobWorker, // Passer le JobWorker r.logger, ) // 3. Handlers authGroup := router.Group("/auth") { authGroup.POST("/register", handlers.Register(authService, r.logger)) authGroup.POST("/login", handlers.Login(authService, sessionService, r.logger)) authGroup.POST("/refresh", handlers.Refresh(authService, r.logger)) authGroup.POST("/verify-email", handlers.VerifyEmail(authService)) authGroup.POST("/resend-verification", handlers.ResendVerification(authService, r.logger)) authGroup.GET("/check-username", handlers.CheckUsername(authService)) // Password reset routes (public) passwordGroup := authGroup.Group("/password") { passwordGroup.POST("/reset-request", handlers.RequestPasswordReset( passwordResetService, passwordService, emailService, r.logger, )) passwordGroup.POST("/reset", handlers.ResetPassword( passwordResetService, passwordService, authService, sessionService, r.logger, )) } // Protected routes (authentification JWT requise) protected := authGroup.Group("") protected.Use(r.config.AuthMiddleware.RequireAuth()) // Changed to RequireAuth() { protected.POST("/logout", handlers.Logout(authService, sessionService, r.logger)) protected.GET("/me", handlers.GetMe()) } } } // setupUserRoutes configure les routes utilisateur func (r *APIRouter) setupUserRoutes(router *gin.RouterGroup) { userRepo := repositories.NewGormUserRepository(r.db.GormDB) userService := services.NewUserServiceWithDB(userRepo, r.db.GormDB) profileHandler := handlers.NewProfileHandler(userService, r.logger) users := router.Group("/users") { users.GET("/:id", profileHandler.GetProfile) users.GET("/by-username/:username", profileHandler.GetProfileByUsername) // Protected routes if r.config.AuthMiddleware != nil { protected := users.Group("") protected.Use(r.config.AuthMiddleware.RequireAuth()) protected.PUT("/:id", profileHandler.UpdateProfile) protected.GET("/:id/completion", profileHandler.GetProfileCompletion) } } } // setupTrackRoutes configure les routes de gestion des tracks func (r *APIRouter) setupTrackRoutes(router *gin.RouterGroup) { uploadDir := r.config.UploadDir if uploadDir == "" { uploadDir = "uploads/tracks" } chunksDir := uploadDir + "/chunks" trackService := trackcore.NewTrackService(r.db.GormDB, r.logger, uploadDir) trackUploadService := services.NewTrackUploadService(r.db.GormDB, r.logger) chunkService := services.NewTrackChunkService(chunksDir, r.logger) likeService := services.NewTrackLikeService(r.db.GormDB, r.logger) streamService := services.NewStreamService(r.config.StreamServerURL, r.logger) trackHandler := trackcore.NewTrackHandler( trackService, trackUploadService, chunkService, likeService, streamService, ) tracks := router.Group("/tracks") { // Public routes tracks.GET("", trackHandler.ListTracks) tracks.GET("/:id", trackHandler.GetTrack) tracks.GET("/:id/stats", trackHandler.GetTrackStats) tracks.GET("/:id/history", trackHandler.GetTrackHistory) tracks.GET("/:id/download", trackHandler.DownloadTrack) tracks.GET("/shared/:token", trackHandler.GetSharedTrack) // Protected routes if r.config.AuthMiddleware != nil { protected := tracks.Group("") protected.Use(r.config.AuthMiddleware.RequireAuth()) // GO-012: Upload track requires creator/premium/admin role uploadGroup := protected.Group("") uploadGroup.Use(r.config.AuthMiddleware.RequireContentCreatorRole()) uploadGroup.POST("", trackHandler.UploadTrack) protected.PUT("/:id", trackHandler.UpdateTrack) protected.DELETE("/:id", trackHandler.DeleteTrack) // Upload protected.GET("/:id/status", trackHandler.GetUploadStatus) protected.POST("/initiate", trackHandler.InitiateChunkedUpload) protected.POST("/chunk", trackHandler.UploadChunk) protected.POST("/complete", trackHandler.CompleteChunkedUpload) protected.GET("/quota/:id", trackHandler.GetUploadQuota) protected.GET("/resume/:uploadId", trackHandler.ResumeUpload) // Batch operations protected.POST("/batch/delete", trackHandler.BatchDeleteTracks) protected.POST("/batch/update", trackHandler.BatchUpdateTracks) // Social protected.POST("/:id/like", trackHandler.LikeTrack) protected.DELETE("/:id/like", trackHandler.UnlikeTrack) protected.GET("/:id/likes", trackHandler.GetTrackLikes) // Sharing protected.POST("/:id/share", trackHandler.CreateShare) protected.DELETE("/share/:id", trackHandler.RevokeShare) } } // Deprecated /internal routes internalDeprecated := router.Group("/internal") internalDeprecated.Use(middleware.DeprecationWarning(r.logger)) { internalDeprecated.POST("/tracks/:id/stream-ready", trackHandler.HandleStreamCallback) } // New /api/v1/internal routes v1Internal := router.Group("/api/v1/internal") { v1Internal.POST("/tracks/:id/stream-ready", trackHandler.HandleStreamCallback) } users := router.Group("/users") { users.GET("/:id/likes", trackHandler.GetUserLikedTracks) } } // setupChatRoutes configure les routes de chat func (r *APIRouter) setupChatRoutes(router *gin.RouterGroup) { chatService := services.NewChatService(r.config.ChatJWTSecret, r.logger) userRepo := repositories.NewGormUserRepository(r.db.GormDB) userService := services.NewUserServiceWithDB(userRepo, r.db.GormDB) chatHandler := handlers.NewChatHandler(chatService, userService, r.logger) chat := router.Group("/chat") { if r.config.AuthMiddleware != nil { chat.Use(r.config.AuthMiddleware.RequireAuth()) chat.POST("/token", chatHandler.GetToken) } } } // setupPlaylistRoutes configure les routes pour les playlists func (r *APIRouter) setupPlaylistRoutes(router *gin.RouterGroup) { playlistRepo := repositories.NewPlaylistRepository(r.db.GormDB) playlistTrackRepo := repositories.NewPlaylistTrackRepository(r.db.GormDB) playlistCollaboratorRepo := repositories.NewPlaylistCollaboratorRepository(r.db.GormDB) userRepo := repositories.NewGormUserRepository(r.db.GormDB) playlistService := services.NewPlaylistService( playlistRepo, playlistTrackRepo, playlistCollaboratorRepo, userRepo, r.logger, ) playlistHandler := handlers.NewPlaylistHandler(playlistService, r.db.GormDB, r.logger) // Protected routes for playlists playlists := router.Group("/playlists") if r.config.AuthMiddleware != nil { playlists.Use(r.config.AuthMiddleware.RequireAuth()) { playlists.GET("", playlistHandler.GetPlaylists) playlists.POST("", playlistHandler.CreatePlaylist) playlists.GET("/:id", playlistHandler.GetPlaylist) playlists.PUT("/:id", playlistHandler.UpdatePlaylist) playlists.DELETE("/:id", playlistHandler.DeletePlaylist) // Playlist Tracks playlists.POST("/:id/tracks", playlistHandler.AddTrack) playlists.DELETE("/:id/tracks/:track_id", playlistHandler.RemoveTrack) playlists.PUT("/:id/tracks/reorder", playlistHandler.ReorderTracks) } } } // setupWebhookRoutes configure les routes pour les webhooks func (r *APIRouter) setupWebhookRoutes(router *gin.RouterGroup) { webhookService := services.NewWebhookService(r.db.GormDB, r.logger, r.config.JWTSecret) webhookWorker := workers.NewWebhookWorker( r.db.GormDB, webhookService, r.logger, 100, // Queue size 5, // Workers 3, // Max retries ) // Start worker in background go webhookWorker.Start(context.Background()) webhookHandler := handlers.NewWebhookHandler(webhookService, webhookWorker, r.logger) webhooks := router.Group("/webhooks") if r.config.AuthMiddleware != nil { webhooks.Use(r.config.AuthMiddleware.RequireAuth()) } { webhooks.POST("", webhookHandler.RegisterWebhook()) webhooks.GET("", webhookHandler.ListWebhooks()) webhooks.DELETE("/:id", webhookHandler.DeleteWebhook()) webhooks.GET("/stats", webhookHandler.GetWebhookStats()) webhooks.POST("/:id/test", webhookHandler.TestWebhook()) } } // setupCorePublicRoutes configure les routes publiques core (health, metrics, upload info) func (r *APIRouter) setupCorePublicRoutes(router *gin.Engine) { // Middleware for deprecated routes deprecated := router.Group("/") deprecated.Use(middleware.DeprecationWarning(r.logger)) // Health check handlers var healthCheckHandler gin.HandlerFunc var livenessHandler gin.HandlerFunc var readinessHandler gin.HandlerFunc if r.db != nil && r.db.GormDB != nil { var redisClient interface{} if r.config != nil { redisClient = r.config.RedisClient } var rabbitMQEventBus interface{} if r.config != nil { rabbitMQEventBus = r.config.RabbitMQEventBus } healthHandler := handlers.NewHealthHandler(r.db.GormDB, r.logger, redisClient, rabbitMQEventBus) healthCheckHandler = healthHandler.Check livenessHandler = healthHandler.Liveness readinessHandler = healthHandler.Readiness } else { healthCheckHandler = handlers.SimpleHealthCheck livenessHandler = handlers.SimpleHealthCheck readinessHandler = handlers.SimpleHealthCheck } // Deprecated Public Core Routes deprecated.GET("/health", healthCheckHandler) deprecated.GET("/healthz", livenessHandler) deprecated.GET("/readyz", readinessHandler) deprecated.GET("/metrics", handlers.PrometheusMetrics()) if r.config != nil && r.config.ErrorMetrics != nil { deprecated.GET("/metrics/aggregated", handlers.AggregatedMetrics(r.config.ErrorMetrics)) } deprecated.GET("/system/metrics", handlers.SystemMetrics) // New /api/v1 Public Core Routes v1Public := router.Group("/api/v1") { v1Public.GET("/health", healthCheckHandler) v1Public.GET("/healthz", livenessHandler) v1Public.GET("/readyz", readinessHandler) // Status endpoint (comprehensive health check) if r.db != nil && r.db.GormDB != nil { var redisClient interface{} if r.config != nil { redisClient = r.config.RedisClient } chatServerURL := "" streamServerURL := "" if r.config != nil { chatServerURL = r.config.ChatServerURL streamServerURL = r.config.StreamServerURL } // Get build info from environment or defaults getEnv := func(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue } version := getEnv("APP_VERSION", "v1.0.0") gitCommit := getEnv("GIT_COMMIT", "unknown") buildTime := getEnv("BUILD_TIME", "") environment := "" if r.config != nil { environment = r.config.Env } statusHandler := handlers.NewStatusHandler( r.db.GormDB, r.logger, redisClient, chatServerURL, streamServerURL, version, gitCommit, buildTime, environment, ) v1Public.GET("/status", statusHandler.GetStatus) } v1Public.GET("/metrics", handlers.PrometheusMetrics()) if r.config != nil && r.config.ErrorMetrics != nil { v1Public.GET("/metrics/aggregated", handlers.AggregatedMetrics(r.config.ErrorMetrics)) } v1Public.GET("/system/metrics", handlers.SystemMetrics) // Upload info endpoints (public, already in /api/v1) if r.db != nil && r.db.GormDB != nil { uploadConfig := services.DefaultUploadConfig() uploadValidator, err := services.NewUploadValidator(uploadConfig, r.logger) if err == nil { auditService := services.NewAuditService(r.db, r.logger) uploadHandler := handlers.NewUploadHandler(uploadValidator, auditService, r.logger) v1Public.GET("/upload/limits", uploadHandler.GetUploadLimits()) v1Public.GET("/upload/validate-type", uploadHandler.ValidateFileType()) } } } } // setupCoreProtectedRoutes configure les routes protégées core (sessions, uploads, audit, admin, conversations) func (r *APIRouter) setupCoreProtectedRoutes(v1 *gin.RouterGroup) { if r.db == nil || r.db.GormDB == nil || r.config == nil { return } // Middleware d'authentification pour routes protégées protected := v1.Group("/") if r.config.AuthMiddleware != nil { protected.Use(r.config.AuthMiddleware.RequireAuth()) } // Services nécessaires sessionService := services.NewSessionService(r.db, r.logger) uploadConfig := services.DefaultUploadConfig() uploadValidator, err := services.NewUploadValidator(uploadConfig, r.logger) if err != nil { r.logger.Error("Failed to create upload validator", zap.Error(err)) return } auditService := services.NewAuditService(r.db, r.logger) // Handlers sessionHandler := handlers.NewSessionHandler(sessionService, auditService, r.logger) uploadHandler := handlers.NewUploadHandler(uploadValidator, auditService, r.logger) auditHandler := handlers.NewAuditHandler(auditService, r.logger) // Routes de session sessions := protected.Group("/sessions") { sessions.POST("/logout", sessionHandler.Logout()) sessions.POST("/logout-all", sessionHandler.LogoutAll()) sessions.GET("/", sessionHandler.GetSessions()) sessions.DELETE("/:session_id", sessionHandler.RevokeSession()) sessions.GET("/stats", sessionHandler.GetSessionStats()) sessions.POST("/refresh", sessionHandler.RefreshSession()) } // Routes d'upload avec rate limiting spécifique uploads := protected.Group("/uploads") { if r.config.RedisClient != nil { uploads.Use(middleware.UploadRateLimit(r.config.RedisClient)) } uploads.POST("/", uploadHandler.UploadFile()) uploads.POST("/batch", uploadHandler.BatchUpload()) uploads.GET("/:id/status", uploadHandler.GetUploadStatus()) uploads.GET("/:id/progress", uploadHandler.UploadProgress()) uploads.DELETE("/:id", uploadHandler.DeleteUpload()) uploads.GET("/stats", uploadHandler.GetUploadStats()) } // Routes d'audit audit := protected.Group("/audit") { audit.GET("/logs", auditHandler.SearchLogs()) audit.GET("/stats", auditHandler.GetStats()) audit.GET("/activity", auditHandler.GetUserActivity()) audit.GET("/suspicious", auditHandler.DetectSuspiciousActivity()) audit.GET("/ip/:ip", auditHandler.GetIPActivity()) audit.GET("/logs/:id", auditHandler.GetAuditLog()) audit.POST("/cleanup", auditHandler.CleanupOldLogs()) } // Routes de conversations (chat rooms) roomRepo := repositories.NewRoomRepository(r.db.GormDB) messageRepo := repositories.NewChatMessageRepository(r.db.GormDB) // New roomService := services.NewRoomService(roomRepo, messageRepo, r.logger) // Updated constructor roomHandler := handlers.NewRoomHandler(roomService, r.logger) conversations := protected.Group("/conversations") { conversations.GET("", roomHandler.GetUserRooms) conversations.POST("", roomHandler.CreateRoom) conversations.GET("/:id", roomHandler.GetRoom) conversations.POST("/:id/members", roomHandler.AddMember) conversations.GET("/:id/history", roomHandler.GetRoomHistory) } // Routes administrateur (avec authentification + permissions admin) admin := v1.Group("/admin") { if r.config.AuthMiddleware != nil { admin.Use(r.config.AuthMiddleware.RequireAuth()) admin.Use(r.config.AuthMiddleware.RequireAdmin()) } // Audit logs (disponibles) admin.GET("/audit/logs", auditHandler.SearchLogs()) admin.GET("/audit/stats", auditHandler.GetStats()) admin.GET("/audit/suspicious", auditHandler.DetectSuspiciousActivity()) } }