188 lines
5.0 KiB
Elixir
188 lines
5.0 KiB
Elixir
defmodule MusicLibrary.Records do
|
|
import Ecto.Query, warn: false
|
|
alias MusicLibrary.Repo
|
|
|
|
alias MusicLibrary.Records.{Record, SearchParser}
|
|
|
|
@fields [:id, :type, :artists, :format, :title, :release, :genres, :musicbrainz_id, :cover_hash]
|
|
|
|
def search_records(query, opts \\ []) do
|
|
limit = Keyword.get(opts, :limit, 20)
|
|
offset = Keyword.get(opts, :offset, 0)
|
|
|
|
search =
|
|
query
|
|
|> build_search()
|
|
|> limit(^limit)
|
|
|> offset(^offset)
|
|
|> select(^@fields)
|
|
|
|
Repo.all(search)
|
|
end
|
|
|
|
def search_records_count(query) do
|
|
search = build_search(query)
|
|
|
|
Repo.aggregate(search, :count)
|
|
end
|
|
|
|
defp build_search(query) do
|
|
{:ok, parsed_query} = SearchParser.parse(query)
|
|
|
|
base_search =
|
|
from r in Record,
|
|
where: not is_nil(r.purchased_at),
|
|
order_by:
|
|
fragment(
|
|
"json_extract(artists, '$[0].sort_name') COLLATE NOCASE ASC, title COLLATE NOCASE ASC"
|
|
)
|
|
|
|
Enum.reduce(parsed_query, base_search, fn
|
|
{:artist, artist}, search ->
|
|
search |> where([r], like(r.artists, ^"%#{artist}%"))
|
|
|
|
{:album, album}, search ->
|
|
search |> where([r], like(r.title, ^"%#{album}%"))
|
|
|
|
{:mbid, mbid}, search ->
|
|
search |> where([r], r.musicbrainz_id == ^mbid or like(r.artists, ^"%#{mbid}%"))
|
|
|
|
{:format, format}, search ->
|
|
search |> where([r], r.format == ^format)
|
|
|
|
{:type, type}, search ->
|
|
search |> where([r], r.type == ^type)
|
|
|
|
{:query, raw_query}, search ->
|
|
search
|
|
|> where(
|
|
[r],
|
|
like(r.title, ^"%#{raw_query}%") or like(r.artists, ^"%#{raw_query}%")
|
|
)
|
|
end)
|
|
end
|
|
|
|
def count_records_by_format do
|
|
q =
|
|
from r in Record,
|
|
where: not is_nil(r.purchased_at),
|
|
group_by: r.format,
|
|
order_by: [desc: count(r.id)],
|
|
select: {r.format, count(r.id)}
|
|
|
|
Repo.all(q)
|
|
end
|
|
|
|
def count_records_by_type do
|
|
q =
|
|
from r in Record,
|
|
where: not is_nil(r.purchased_at),
|
|
group_by: r.type,
|
|
order_by: [desc: count(r.id)],
|
|
select: {r.type, count(r.id)}
|
|
|
|
Repo.all(q)
|
|
end
|
|
|
|
def get_record!(id), do: Repo.get!(Record, id)
|
|
|
|
def get_latest_record! do
|
|
q =
|
|
from r in Record,
|
|
where: not is_nil(r.purchased_at),
|
|
order_by: [desc: r.purchased_at],
|
|
limit: 1,
|
|
select: ^@fields
|
|
|
|
Repo.one!(q)
|
|
end
|
|
|
|
def get_cover(id) do
|
|
q =
|
|
from r in Record,
|
|
where: r.id == ^id,
|
|
select: %{cover_data: r.cover_data, cover_hash: r.cover_hash}
|
|
|
|
Repo.one(q)
|
|
end
|
|
|
|
def search_release_group(query, opts \\ []) do
|
|
limit = Keyword.get(opts, :limit, 20)
|
|
offset = Keyword.get(opts, :offset, 0)
|
|
musicbrainz().search_release_group(query, limit: limit, offset: offset)
|
|
end
|
|
|
|
def import_from_musicbrainz(musicbrainz_id, opts \\ []) do
|
|
with format = Keyword.get(opts, :format, "cd"),
|
|
purchased_at = Keyword.get(opts, :purchased_at),
|
|
{:ok, release_group} <- musicbrainz().get_release_group(musicbrainz_id),
|
|
{:ok, cover_data} <- musicbrainz().get_cover_art(musicbrainz_id),
|
|
record_params = build_record_params(release_group, cover_data, format, purchased_at) do
|
|
create_record(record_params)
|
|
else
|
|
error -> error
|
|
end
|
|
end
|
|
|
|
defp build_record_params(release_group, cover_data, format, purchased_at) do
|
|
musicbrainz_id = release_group["id"]
|
|
|
|
artists_attrs =
|
|
release_group
|
|
|> get_in(["artist-credit", Access.all(), "artist"])
|
|
|> Enum.map(fn artist ->
|
|
%{
|
|
name: artist["name"],
|
|
musicbrainz_id: artist["id"],
|
|
sort_name: artist["sort-name"],
|
|
disambiguation: artist["disambiguation"]
|
|
}
|
|
end)
|
|
|
|
%{
|
|
"musicbrainz_id" => musicbrainz_id,
|
|
"musicbrainz_data" => release_group,
|
|
"title" => release_group["title"],
|
|
"artists" => artists_attrs,
|
|
"release" => release_group["first-release-date"],
|
|
"type" => parse_subtype(release_group["primary-type"]),
|
|
"format" => format,
|
|
"genres" => Enum.map(release_group["genres"], fn g -> g["name"] end),
|
|
"cover_url" => "https://coverartarchive.org/release-group/#{musicbrainz_id}/front",
|
|
"cover_data" => cover_data,
|
|
"purchased_at" => purchased_at
|
|
}
|
|
end
|
|
|
|
defp parse_subtype("Album"), do: :album
|
|
defp parse_subtype("EP"), do: :ep
|
|
defp parse_subtype("Live"), do: :live
|
|
defp parse_subtype("Compilation"), do: :compilation
|
|
defp parse_subtype("Single"), do: :single
|
|
defp parse_subtype(_), do: :other
|
|
|
|
def create_record(attrs \\ %{}) do
|
|
%Record{}
|
|
|> Record.changeset(attrs)
|
|
|> Repo.insert()
|
|
end
|
|
|
|
def update_record(%Record{} = record, attrs) do
|
|
record
|
|
|> Record.changeset(attrs)
|
|
|> Repo.update()
|
|
end
|
|
|
|
def delete_record(%Record{} = record) do
|
|
Repo.delete(record)
|
|
end
|
|
|
|
def change_record(%Record{} = record, attrs \\ %{}) do
|
|
Record.changeset(record, attrs)
|
|
end
|
|
|
|
defp musicbrainz do
|
|
Application.get_env(:music_library, :musicbrainz, MusicBrainz.APIImpl)
|
|
end
|
|
end
|