[BE-API-006] be-api: Implement chat stats endpoint

- Added GetStats method to ChatService with database access
- Returns active_users (distinct users who sent messages in last 24h)
- Returns total_messages (non-deleted messages count)
- Returns rooms_active (rooms with messages in last 24h)
- Added GetStats handler and GET /chat/stats route
- Updated ChatService to use NewChatServiceWithDB for database access

Phase: PHASE-2
Priority: P1
Progress: 15/267 (5.6%)
This commit is contained in:
senke 2025-12-23 01:51:49 +01:00
parent 418d2b064c
commit a3c055efbb
4 changed files with 93 additions and 2 deletions

View file

@ -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": [
{

View file

@ -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
}
}
}

View file

@ -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)
}

View file

@ -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
}