2025-12-03 19:29:37 +00:00
package services
import (
"context"
"fmt"
"strings"
"time"
2026-02-14 21:50:23 +00:00
"veza-backend-api/internal/database"
2025-12-03 19:29:37 +00:00
"veza-backend-api/internal/models"
2026-02-14 21:50:23 +00:00
"gorm.io/gorm"
2025-12-03 19:29:37 +00:00
)
// TrackSearchParams représente les paramètres de recherche de tracks
type TrackSearchParams struct {
Query string
Tags [ ] string
TagMode string // "AND" or "OR"
MinDuration * int // seconds
MaxDuration * int // seconds
MinBPM * int
MaxBPM * int
Genre * string
Format * string
MinDate * string // ISO date
MaxDate * string // ISO date
Page int
Limit int
SortBy string
SortOrder string
}
// TrackSearchService gère la recherche avancée de tracks
type TrackSearchService struct {
db * gorm . DB
}
// NewTrackSearchService crée un nouveau service de recherche de tracks
func NewTrackSearchService ( db * gorm . DB ) * TrackSearchService {
return & TrackSearchService { db : db }
}
2026-02-14 21:50:23 +00:00
// NewTrackSearchServiceWithDB crée un service de recherche avec support read replica
func NewTrackSearchServiceWithDB ( db * database . Database ) * TrackSearchService {
return & TrackSearchService { db : db . ForRead ( ) }
}
2025-12-03 19:29:37 +00:00
// SearchTracks effectue une recherche avancée de tracks avec support de filtres combinés
func ( s * TrackSearchService ) SearchTracks ( ctx context . Context , params TrackSearchParams ) ( [ ] * models . Track , int64 , error ) {
query := s . db . Model ( & models . Track { } ) . Where ( "is_public = ? AND deleted_at IS NULL" , true )
// Full-text search on title, artist, album
if params . Query != "" {
searchTerm := "%" + strings . ToLower ( params . Query ) + "%"
query = query . Where (
"LOWER(title) LIKE ? OR LOWER(artist) LIKE ? OR LOWER(album) LIKE ?" ,
searchTerm , searchTerm , searchTerm ,
)
}
// Tag search - Note: Tags field not in current model, skipping for now
// This can be implemented when tags are added to the Track model
if len ( params . Tags ) > 0 {
// Tags functionality would go here when Tags field is added
// For now, we'll skip tag filtering
}
// Duration filter (supports combined min/max)
if params . MinDuration != nil && params . MaxDuration != nil {
// Validate that min <= max
if * params . MinDuration <= * params . MaxDuration {
query = query . Where ( "duration >= ? AND duration <= ?" , * params . MinDuration , * params . MaxDuration )
}
} else if params . MinDuration != nil {
query = query . Where ( "duration >= ?" , * params . MinDuration )
} else if params . MaxDuration != nil {
query = query . Where ( "duration <= ?" , * params . MaxDuration )
}
// BPM filter - Note: BPM field not in current model, skipping for now
// This can be implemented when BPM field is added to the Track model
if params . MinBPM != nil || params . MaxBPM != nil {
// BPM functionality would go here when BPM field is added
// When implemented, should support combined min/max like duration
}
// Genre filter (case-insensitive)
if params . Genre != nil && * params . Genre != "" {
query = query . Where ( "LOWER(genre) = ?" , strings . ToLower ( strings . TrimSpace ( * params . Genre ) ) )
}
// Format filter (case-insensitive)
if params . Format != nil && * params . Format != "" {
query = query . Where ( "LOWER(format) = ?" , strings . ToLower ( strings . TrimSpace ( * params . Format ) ) )
}
// Date range filter (supports combined min/max)
if params . MinDate != nil && * params . MinDate != "" {
minDate , err := time . Parse ( time . RFC3339 , * params . MinDate )
if err == nil {
query = query . Where ( "created_at >= ?" , minDate )
}
}
if params . MaxDate != nil && * params . MaxDate != "" {
maxDate , err := time . Parse ( time . RFC3339 , * params . MaxDate )
if err == nil {
query = query . Where ( "created_at <= ?" , maxDate )
}
}
// Count total before pagination
var total int64
if err := query . Count ( & total ) . Error ; err != nil {
return nil , 0 , fmt . Errorf ( "failed to count tracks: %w" , err )
}
// Apply sorting with computed fields
sortOrder := "DESC"
if params . SortOrder == "asc" {
sortOrder = "ASC"
}
sortBy := params . SortBy
if sortBy == "" {
sortBy = "created_at"
}
// Handle different sorting options
switch sortBy {
case "popularity" :
// Sort by like_count (popularity)
query = query . Order ( fmt . Sprintf ( "like_count %s" , sortOrder ) )
case "play_count" :
// Sort by play_count (total plays)
query = query . Order ( fmt . Sprintf ( "play_count %s" , sortOrder ) )
case "comment_count" :
// Sort by number of comments (requires join and count)
query = query . Select ( "tracks.*, COALESCE(comment_counts.count, 0) as comment_count" ) .
Joins ( "LEFT JOIN (SELECT track_id, COUNT(*) as count FROM track_comments WHERE deleted_at IS NULL GROUP BY track_id) as comment_counts ON comment_counts.track_id = tracks.id" ) .
Order ( fmt . Sprintf ( "comment_count %s" , sortOrder ) )
case "title" :
// Sort by title alphabetically (case-insensitive)
query = query . Order ( fmt . Sprintf ( "LOWER(title) %s" , sortOrder ) )
case "artist" :
// Sort by artist alphabetically (case-insensitive)
query = query . Order ( fmt . Sprintf ( "LOWER(artist) %s" , sortOrder ) )
case "created_at" , "updated_at" , "duration" :
// Direct field sorting
query = query . Order ( fmt . Sprintf ( "%s %s" , sortBy , sortOrder ) )
case "like_count" :
// Sort by like_count (same as popularity)
query = query . Order ( fmt . Sprintf ( "like_count %s" , sortOrder ) )
default :
// Default to created_at
query = query . Order ( fmt . Sprintf ( "created_at %s" , sortOrder ) )
}
// Apply pagination
if params . Page < 1 {
params . Page = 1
}
if params . Limit < 1 {
params . Limit = 20
}
if params . Limit > 100 {
params . Limit = 100 // Max limit
}
offset := ( params . Page - 1 ) * params . Limit
query = query . Offset ( offset ) . Limit ( params . Limit )
var tracks [ ] * models . Track
if err := query . Find ( & tracks ) . Error ; err != nil {
return nil , 0 , fmt . Errorf ( "failed to search tracks: %w" , err )
}
return tracks , total , nil
}