package services import ( "context" "fmt" "veza-backend-api/internal/database" "go.uber.org/zap" ) // SearchService handles search operations type SearchService struct { db *database.Database logger *zap.Logger } // SearchResult represents search results type SearchResult struct { Tracks []TrackResult `json:"tracks"` Users []UserResult `json:"users"` Playlists []PlaylistResult `json:"playlists"` } type TrackResult struct { ID string `json:"id"` Title string `json:"title"` Artist string `json:"artist"` URL string `json:"url"` } type UserResult struct { ID string `json:"id"` Username string `json:"username"` Avatar string `json:"avatar"` } type PlaylistResult struct { ID string `json:"id"` Name string `json:"name"` Cover string `json:"cover"` } // NewSearchService creates a new search service func NewSearchService(db *database.Database, logger *zap.Logger) *SearchService { return &SearchService{ db: db, logger: logger, } } // Search performs a full-text search func (ss *SearchService) Search(query string, types []string) (*SearchResult, error) { ctx := context.Background() results := &SearchResult{} // Build search types - if empty, search all searchAll := len(types) == 0 searchTracks := searchAll || contains(types, "track") searchUsers := searchAll || contains(types, "user") searchPlaylists := searchAll || contains(types, "playlist") // Search tracks if searchTracks { rows, err := ss.db.QueryContext(ctx, ` SELECT id, title, artist, url FROM tracks WHERE title ILIKE $1 OR artist ILIKE $1 LIMIT 10 `, "%"+query+"%") if err != nil { return nil, fmt.Errorf("failed to search tracks: %w", err) } defer rows.Close() for rows.Next() { var track TrackResult if err := rows.Scan(&track.ID, &track.Title, &track.Artist, &track.URL); err != nil { continue } results.Tracks = append(results.Tracks, track) } } // Search users if searchUsers { rows, err := ss.db.QueryContext(ctx, ` SELECT id, username, avatar FROM users WHERE username ILIKE $1 LIMIT 10 `, "%"+query+"%") if err != nil { return nil, fmt.Errorf("failed to search users: %w", err) } defer rows.Close() for rows.Next() { var user UserResult if err := rows.Scan(&user.ID, &user.Username, &user.Avatar); err != nil { continue } results.Users = append(results.Users, user) } } // Search playlists if searchPlaylists { rows, err := ss.db.QueryContext(ctx, ` SELECT id, name, cover_image_url FROM playlists WHERE name ILIKE $1 AND is_public = TRUE LIMIT 10 `, "%"+query+"%") if err != nil { return nil, fmt.Errorf("failed to search playlists: %w", err) } defer rows.Close() for rows.Next() { var playlist PlaylistResult if err := rows.Scan(&playlist.ID, &playlist.Name, &playlist.Cover); err != nil { continue } results.Playlists = append(results.Playlists, playlist) } } return results, nil } func contains(slice []string, item string) bool { for _, s := range slice { if s == item { return true } } return false }