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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTrackStats récupère les statistiques d'un track
|
// 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) { // Changed trackID to uuid.UUID
|
func (s *AnalyticsService) GetTrackStats(ctx context.Context, trackID uuid.UUID) (*types.TrackStats, error) {
|
||||||
var stats types.TrackStats
|
var result struct {
|
||||||
|
TrackDuration int `gorm:"column:track_duration"`
|
||||||
// Vérifier que le track existe
|
TotalPlays int64 `gorm:"column:total_plays"`
|
||||||
var track models.Track
|
UniqueListeners int64 `gorm:"column:unique_listeners"`
|
||||||
if err := s.db.WithContext(ctx).First(&track, "id = ?", trackID).Error; err != nil { // Updated query
|
AvgDuration float64 `gorm:"column:avg_duration"`
|
||||||
if err == gorm.ErrRecordNotFound {
|
CompletedPlays int64 `gorm:"column:completed_plays"`
|
||||||
return nil, errors.New("track not found")
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("failed to get track: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Total plays
|
tx := s.db.WithContext(ctx).Raw(`
|
||||||
if err := s.db.WithContext(ctx).Model(&models.TrackPlay{}).
|
SELECT
|
||||||
Where("track_id = ?", trackID).
|
t.duration AS track_duration,
|
||||||
Count(&stats.TotalPlays).Error; err != nil {
|
CAST(COUNT(tp.id) AS INTEGER) AS total_plays,
|
||||||
return nil, fmt.Errorf("failed to count total plays: %w", err)
|
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)
|
stats := &types.TrackStats{
|
||||||
if err := s.db.WithContext(ctx).Model(&models.TrackPlay{}).
|
TotalPlays: result.TotalPlays,
|
||||||
Where("track_id = ? AND user_id IS NOT NULL", trackID).
|
UniqueListeners: result.UniqueListeners,
|
||||||
Distinct("user_id").
|
AverageDuration: result.AvgDuration,
|
||||||
Count(&stats.UniqueListeners).Error; err != nil {
|
CompletionRate: 0,
|
||||||
return nil, fmt.Errorf("failed to count unique listeners: %w", err)
|
|
||||||
}
|
}
|
||||||
|
if result.TotalPlays > 0 {
|
||||||
// Average duration
|
stats.CompletionRate = float64(result.CompletedPlays) / float64(result.TotalPlays) * 100
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
stats.AverageDuration = avgDuration
|
return stats, nil
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPlaysOverTime récupère les lectures sur une période pour un graphique temporel
|
// GetPlaysOverTime récupère les lectures sur une période pour un graphique temporel
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue