diff --git a/VEZA_COMPLETE_MVP_TODOLIST.json b/VEZA_COMPLETE_MVP_TODOLIST.json index 7f7803485..956ae0385 100644 --- a/VEZA_COMPLETE_MVP_TODOLIST.json +++ b/VEZA_COMPLETE_MVP_TODOLIST.json @@ -1378,7 +1378,19 @@ "description": "GET /api/v1/chat/stats returns active_users, total_messages, rooms_active", "owner": "backend", "estimated_hours": 2, - "status": "todo", + "status": "completed", + "completion": { + "completed_at": "2025-12-23T00:51:44Z", + "actual_hours": 0.5, + "commits": [], + "files_changed": [ + "veza-backend-api/internal/services/chat_service.go", + "veza-backend-api/internal/handlers/chat_handler.go", + "veza-backend-api/internal/api/router.go" + ], + "notes": "Added GetStats method to ChatService with database access. Returns active_users (distinct users who sent messages in last 24h), total_messages (non-deleted messages), and rooms_active (rooms with messages in last 24h). Added GetStats handler and GET /chat/stats route. Updated ChatService to use NewChatServiceWithDB for database access.", + "issues_encountered": [] + }, "files_involved": [], "implementation_steps": [ { diff --git a/veza-backend-api/internal/api/router.go b/veza-backend-api/internal/api/router.go index 3d1430594..3eaf5f590 100644 --- a/veza-backend-api/internal/api/router.go +++ b/veza-backend-api/internal/api/router.go @@ -500,7 +500,8 @@ func (r *APIRouter) setupTrackRoutes(router *gin.RouterGroup) { // setupChatRoutes configure les routes de chat func (r *APIRouter) setupChatRoutes(router *gin.RouterGroup) { - chatService := services.NewChatService(r.config.ChatJWTSecret, r.logger) + // BE-API-006: Use NewChatServiceWithDB to enable stats functionality + chatService := services.NewChatServiceWithDB(r.config.ChatJWTSecret, r.db.GormDB, r.logger) userRepo := repositories.NewGormUserRepository(r.db.GormDB) userService := services.NewUserServiceWithDB(userRepo, r.db.GormDB) @@ -511,6 +512,7 @@ func (r *APIRouter) setupChatRoutes(router *gin.RouterGroup) { if r.config.AuthMiddleware != nil { chat.Use(r.config.AuthMiddleware.RequireAuth()) chat.POST("/token", chatHandler.GetToken) + chat.GET("/stats", chatHandler.GetStats) // BE-API-006: Chat stats endpoint } } } diff --git a/veza-backend-api/internal/handlers/chat_handler.go b/veza-backend-api/internal/handlers/chat_handler.go index 84079ebc4..f719b5573 100644 --- a/veza-backend-api/internal/handlers/chat_handler.go +++ b/veza-backend-api/internal/handlers/chat_handler.go @@ -7,6 +7,7 @@ import ( "github.com/gin-gonic/gin" "go.uber.org/zap" + apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/services" ) @@ -66,3 +67,16 @@ func (h *ChatHandler) GetToken(c *gin.Context) { RespondSuccess(c, http.StatusOK, token) } + +// GetStats returns chat statistics +// BE-API-006: Implement chat stats endpoint +func (h *ChatHandler) GetStats(c *gin.Context) { + stats, err := h.chatService.GetStats(c.Request.Context()) + if err != nil { + h.logger.Error("Failed to get chat stats", zap.Error(err)) + RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to get chat stats", err)) + return + } + + RespondSuccess(c, http.StatusOK, stats) +} diff --git a/veza-backend-api/internal/services/chat_service.go b/veza-backend-api/internal/services/chat_service.go index 21811ae84..73ac69fdd 100644 --- a/veza-backend-api/internal/services/chat_service.go +++ b/veza-backend-api/internal/services/chat_service.go @@ -1,6 +1,7 @@ package services import ( + "context" "errors" "fmt" "github.com/google/uuid" @@ -8,11 +9,14 @@ import ( "github.com/golang-jwt/jwt/v5" "go.uber.org/zap" + "gorm.io/gorm" + "veza-backend-api/internal/models" ) type ChatService struct { jwtSecret string logger *zap.Logger + db *gorm.DB } func NewChatService(jwtSecret string, logger *zap.Logger) *ChatService { @@ -25,6 +29,18 @@ func NewChatService(jwtSecret string, logger *zap.Logger) *ChatService { } } +// NewChatServiceWithDB crée un nouveau ChatService avec accès à la base de données +func NewChatServiceWithDB(jwtSecret string, db *gorm.DB, logger *zap.Logger) *ChatService { + if logger == nil { + logger = zap.NewNop() + } + return &ChatService{ + jwtSecret: jwtSecret, + logger: logger, + db: db, + } +} + type ChatTokenResponse struct { Token string `json:"token"` ExpiresIn int64 `json:"expires_in"` @@ -61,3 +77,50 @@ func (s *ChatService) GenerateToken(userID uuid.UUID, username string) (*ChatTok WSUrl: "/ws", // Relative path, frontend appends base URL }, nil } + +// ChatStats représente les statistiques du chat +type ChatStats struct { + ActiveUsers int64 `json:"active_users"` + TotalMessages int64 `json:"total_messages"` + RoomsActive int64 `json:"rooms_active"` +} + +// GetStats récupère les statistiques du chat +// BE-API-006: Implement chat stats endpoint +func (s *ChatService) GetStats(ctx context.Context) (*ChatStats, error) { + if s.db == nil { + return nil, errors.New("database connection not available") + } + + stats := &ChatStats{} + + // Total messages (non supprimés) + if err := s.db.WithContext(ctx).Model(&models.Message{}). + Where("is_deleted = ?", false). + Count(&stats.TotalMessages).Error; err != nil { + return nil, fmt.Errorf("failed to count total messages: %w", err) + } + + // Active users: utilisateurs distincts qui ont envoyé des messages dans les dernières 24h + activeSince := time.Now().Add(-24 * time.Hour) + var activeUsersCount int64 + if err := s.db.WithContext(ctx).Model(&models.Message{}). + Where("is_deleted = ? AND created_at >= ?", false, activeSince). + Distinct("user_id"). + Count(&activeUsersCount).Error; err != nil { + return nil, fmt.Errorf("failed to count active users: %w", err) + } + stats.ActiveUsers = activeUsersCount + + // Rooms actives: rooms qui ont eu des messages dans les dernières 24h + var roomsActiveCount int64 + if err := s.db.WithContext(ctx).Model(&models.Message{}). + Where("is_deleted = ? AND created_at >= ?", false, activeSince). + Distinct("room_id"). + Count(&roomsActiveCount).Error; err != nil { + return nil, fmt.Errorf("failed to count active rooms: %w", err) + } + stats.RoomsActive = roomsActiveCount + + return stats, nil +}