Files
music_library/lib/music_library/search.ex
T
Claudio Ortolina 901a6b1c93 Update dependencies
credo 1.7.12 => 1.7.13
usage_rules 0.1.24 => 0.1.25
2025-10-15 09:57:13 +01:00

123 lines
3.1 KiB
Elixir

defmodule MusicLibrary.Search do
@moduledoc """
Universal search functionality across Records and Artists.
This module provides unified search across:
- Records in Collection (purchased records)
- Records in Wishlist (unpurchased records)
- Artists
"""
import Ecto.Query, warn: false
alias MusicLibrary.Artists.ArtistInfo
alias MusicLibrary.{Collection, Repo, Wishlist}
alias MusicLibrary.Records.ArtistRecord
@doc """
Performs a universal search across all entity types.
Returns a map with grouped results:
- :collection - Records in the collection
- :wishlist - Records in the wishlist
- :artists - Artists
Options:
- :limit - Limit per category (default: 5)
"""
def universal_search(query, opts \\ []) do
limit = Keyword.get(opts, :limit, 5)
%{
collection: search_collection(query, limit),
wishlist: search_wishlist(query, limit),
artists: search_artists(query, limit)
}
end
@doc """
Searches records in the collection (purchased records).
"""
def search_collection(query, limit \\ 5) do
Collection.search_records(query, limit: limit)
end
@doc """
Searches records in the wishlist (unpurchased records).
"""
def search_wishlist(query, limit \\ 5) do
Wishlist.search_records(query, limit: limit)
end
@doc """
Searches artists by name.
Searches across artist names in the artist_records table,
returning distinct artists that match the query.
"""
def search_artists(query, limit \\ 5) do
case String.trim(query) do
"" ->
[]
trimmed_query ->
normalized_query = String.downcase(trimmed_query)
q =
from ar in ArtistRecord,
join: ai in ArtistInfo,
on: ar.musicbrainz_id == ai.id,
where:
fragment("lower(unaccent(artist ->> '$.name')) LIKE ?", ^"%#{normalized_query}%"),
group_by: ar.musicbrainz_id,
select: %{artist: ar.artist, image_data_hash: ai.image_data_hash},
limit: ^limit,
order_by: fragment("artist ->> '$.name'")
Repo.all(q)
end
end
@doc """
Gets the count of search results for each category.
Returns a map with counts for each category:
- :collection_count
- :wishlist_count
- :artists_count
"""
def search_counts(query) do
%{
collection_count: Collection.search_records_count(query),
wishlist_count: Wishlist.search_records_count(query),
artists_count: search_artists_count(query)
}
end
@doc """
Gets the count of artists matching the search query.
"""
def search_artists_count(query) do
case String.trim(query) do
"" ->
0
trimmed_query ->
normalized_query = String.downcase(trimmed_query)
# Use a subquery to count distinct musicbrainz_ids
subquery =
from ar in ArtistRecord,
where: fragment("lower(artist ->> '$.name') LIKE ?", ^"%#{normalized_query}%"),
select: ar.musicbrainz_id,
distinct: true
q =
from s in subquery(subquery),
select: count()
Repo.one(q) || 0
end
end
end