veza/veza-backend-api/internal/api/router.go
2025-12-03 20:29:37 +01:00

528 lines
No EOL
18 KiB
Go

package api
import (
"context"
"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"
"veza-backend-api/internal/core/marketplace"
"veza-backend-api/internal/services"
authcore "veza-backend-api/internal/core/auth"
trackcore "veza-backend-api/internal/core/track"
"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.Recovery(r.logger))
if r.config != nil && len(r.config.CORSOrigins) > 0 {
router.Use(middleware.CORS(r.config.CORSOrigins))
} else {
router.Use(middleware.CORSDefault())
}
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)
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)
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,
emailService,
r.logger,
)
// 3. Handlers
authGroup := router.Group("/auth")
{
authGroup.POST("/register", handlers.Register(authService))
authGroup.POST("/login", handlers.Login(authService, sessionService, r.logger))
authGroup.POST("/refresh", handlers.Refresh(authService))
authGroup.POST("/verify-email", handlers.VerifyEmail(authService))
authGroup.POST("/resend-verification", handlers.ResendVerification(authService))
authGroup.GET("/check-username", handlers.CheckUsername(authService))
// Protected routes (authentification JWT requise)
protected := authGroup.Group("")
protected.Use(r.config.AuthMiddleware.RequireAuth()) // Changed to RequireAuth()
{
protected.POST("/logout", handlers.Logout(authService, sessionService))
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)
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)
// 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)
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())
}
}