perf(analytics): optimize GetTrackStats to single query
This commit is contained in:
parent
759154e660
commit
7de106b2dc
1 changed files with 33 additions and 45 deletions
|
|
@ -91,57 +91,45 @@ func (s *AnalyticsService) RecordPlay(ctx context.Context, trackID uuid.UUID, us
|
|||
return nil
|
||||
}
|
||||
|
||||
// GetTrackStats récupère les statistiques d'un track
|
||||
func (s *AnalyticsService) GetTrackStats(ctx context.Context, trackID uuid.UUID) (*types.TrackStats, error) { // Changed trackID to uuid.UUID
|
||||
var stats types.TrackStats
|
||||
|
||||
// Vérifier que le track existe
|
||||
var track models.Track
|
||||
if err := s.db.WithContext(ctx).First(&track, "id = ?", trackID).Error; err != nil { // Updated query
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New("track not found")
|
||||
}
|
||||
return nil, fmt.Errorf("failed to get track: %w", err)
|
||||
// GetTrackStats récupère les statistiques d'un track (optimisé: 1 requête au lieu de 5)
|
||||
func (s *AnalyticsService) GetTrackStats(ctx context.Context, trackID uuid.UUID) (*types.TrackStats, error) {
|
||||
var result struct {
|
||||
TrackDuration int `gorm:"column:track_duration"`
|
||||
TotalPlays int64 `gorm:"column:total_plays"`
|
||||
UniqueListeners int64 `gorm:"column:unique_listeners"`
|
||||
AvgDuration float64 `gorm:"column:avg_duration"`
|
||||
CompletedPlays int64 `gorm:"column:completed_plays"`
|
||||
}
|
||||
|
||||
// Total plays
|
||||
if err := s.db.WithContext(ctx).Model(&models.TrackPlay{}).
|
||||
Where("track_id = ?", trackID).
|
||||
Count(&stats.TotalPlays).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to count total plays: %w", err)
|
||||
tx := s.db.WithContext(ctx).Raw(`
|
||||
SELECT
|
||||
t.duration AS track_duration,
|
||||
CAST(COUNT(tp.id) AS INTEGER) AS total_plays,
|
||||
CAST(COUNT(DISTINCT CASE WHEN tp.user_id IS NOT NULL THEN tp.user_id END) AS INTEGER) AS unique_listeners,
|
||||
COALESCE(AVG(tp.duration), 0) AS avg_duration,
|
||||
CAST(COUNT(CASE WHEN t.duration > 0 AND tp.duration >= t.duration * 0.9 THEN 1 END) AS INTEGER) AS completed_plays
|
||||
FROM tracks t
|
||||
LEFT JOIN track_plays tp ON tp.track_id = t.id
|
||||
WHERE t.id = ?
|
||||
GROUP BY t.id, t.duration
|
||||
`, trackID).Scan(&result)
|
||||
if tx.Error != nil {
|
||||
return nil, fmt.Errorf("failed to get track stats: %w", tx.Error)
|
||||
}
|
||||
if tx.RowsAffected == 0 {
|
||||
return nil, errors.New("track not found")
|
||||
}
|
||||
|
||||
// Unique listeners (distinct user_id, en excluant NULL)
|
||||
if err := s.db.WithContext(ctx).Model(&models.TrackPlay{}).
|
||||
Where("track_id = ? AND user_id IS NOT NULL", trackID).
|
||||
Distinct("user_id").
|
||||
Count(&stats.UniqueListeners).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to count unique listeners: %w", err)
|
||||
stats := &types.TrackStats{
|
||||
TotalPlays: result.TotalPlays,
|
||||
UniqueListeners: result.UniqueListeners,
|
||||
AverageDuration: result.AvgDuration,
|
||||
CompletionRate: 0,
|
||||
}
|
||||
|
||||
// Average duration
|
||||
var avgDuration float64
|
||||
if err := s.db.WithContext(ctx).Model(&models.TrackPlay{}).
|
||||
Where("track_id = ?", trackID).
|
||||
Select("COALESCE(AVG(duration), 0)").
|
||||
Scan(&avgDuration).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to calculate average duration: %w", err)
|
||||
if result.TotalPlays > 0 {
|
||||
stats.CompletionRate = float64(result.CompletedPlays) / float64(result.TotalPlays) * 100
|
||||
}
|
||||
stats.AverageDuration = avgDuration
|
||||
|
||||
// Completion rate (90% de la durée du track)
|
||||
if track.Duration > 0 && stats.TotalPlays > 0 {
|
||||
var completedPlays int64
|
||||
completionThreshold := int(float64(track.Duration) * 0.9)
|
||||
if err := s.db.WithContext(ctx).Model(&models.TrackPlay{}).
|
||||
Where("track_id = ? AND duration >= ?", trackID, completionThreshold).
|
||||
Count(&completedPlays).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to count completed plays: %w", err)
|
||||
}
|
||||
stats.CompletionRate = float64(completedPlays) / float64(stats.TotalPlays) * 100
|
||||
}
|
||||
|
||||
return &stats, nil
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// GetPlaysOverTime récupère les lectures sur une période pour un graphique temporel
|
||||
|
|
|
|||
Loading…
Reference in a new issue