Can search and import records

Requires refactor and cleanup, along with integration tests
This commit is contained in:
Claudio Ortolina
2024-09-23 20:27:40 +01:00
parent 7ebc12c4ac
commit d97f21a8ef
6 changed files with 248 additions and 3 deletions
@@ -24,6 +24,12 @@ defmodule MusicLibraryWeb.RecordLive.Index do
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
end
defp apply_action(socket, :search, _params) do
socket
|> assign(:page_title, "Search Records")
|> assign(:record, nil)
end
defp apply_action(socket, :edit, %{"id" => id}) do
socket
|> assign(:page_title, "Edit Record")
@@ -62,6 +68,10 @@ defmodule MusicLibraryWeb.RecordLive.Index do
{:noreply, stream_insert(socket, :records, record)}
end
def handle_info({__MODULE__, {:saved, record}}, socket) do
{:noreply, push_navigate(socket, to: ~p"/records/#{record}")}
end
@impl true
def handle_event("delete", %{"id" => id}, socket) do
record = Records.get_record!(id)
@@ -70,6 +80,70 @@ defmodule MusicLibraryWeb.RecordLive.Index do
{:noreply, stream_delete(socket, :records, record)}
end
def handle_event("import", %{"id" => musicbrainz_id}, socket) do
with {:ok, result} <- MusicLibrary.Records.MusicBrainz.get_release_group(musicbrainz_id),
{:ok, image_data} <- MusicLibrary.Records.MusicBrainz.get_cover_art(musicbrainz_id) do
artists_attrs =
result
|> 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)
record_attrs = %{
"musicbrainz_id" => musicbrainz_id,
"title" => result["title"],
"artists" => artists_attrs,
"year" => parse_year(result["first-release-date"]),
"type" => parse_subtype(result["primary-type"]),
"genres" => Enum.map(result["genres"], fn g -> g["name"] end),
"image_url" => "https://coverartarchive.org/release-group/#{musicbrainz_id}/front",
"image_data" => image_data
}
case Records.create_record(record_attrs) do
{:ok, record} ->
notify_parent({:saved, record})
{:noreply,
socket
|> put_flash(:info, "Record imported successfully")
|> push_patch(to: ~p"/records")}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, form: to_form(changeset))}
end
else
{:error, _} ->
{:noreply, socket}
end
end
defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
defp parse_year(iso_date) when is_binary(iso_date) do
case Date.from_iso8601(iso_date) do
{:ok, date} ->
date.year
_error ->
{year, _rest} = Integer.parse(iso_date)
{:ok, year}
end
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
defp musicbrainz_url(record) do
"https://musicbrainz.org/release-group/#{record.musicbrainz_id}"
end
@@ -4,6 +4,9 @@
<.link patch={~p"/records/new"}>
<.button>New Record</.button>
</.link>
<.link patch={~p"/records/search"}>
<.button>Search Record</.button>
</.link>
</:actions>
</.header>
@@ -55,4 +58,16 @@
/>
</.modal>
<.modal :if={@live_action == :search} id="record-modal" show on_cancel={JS.patch(~p"/records")}>
<.live_component
module={MusicLibraryWeb.RecordLive.SearchComponent}
id={:search}
title={@page_title}
action={@live_action}
record={@record}
patch={~p"/records"}
initial_query=""
/>
</.modal>
<.pagination pagination_params={@pagination_params} />
@@ -0,0 +1,122 @@
defmodule MusicLibraryWeb.RecordLive.SearchComponent do
use MusicLibraryWeb, :live_component
alias MusicLibrary.Records.MusicBrainz
@impl true
def render(assigns) do
~H"""
<div>
<.simple_form
for={@form}
id="search-form"
phx-target={@myself}
phx-change="search"
phx-submit="search"
>
<.input
field={@form[:query]}
type="text"
label="Search"
prompt="Search for records"
phx-debounce="500"
/>
</.simple_form>
<ul role="list" class="divide-y divide-gray-100">
<.result :for={record <- @records} record={record} />
</ul>
</div>
"""
end
defp result(assigns) do
~H"""
<li
class="flex justify-between gap-x-6 py-5 cursor-pointer hover:bg-gray-50"
phx-click={JS.push("import", value: %{id: @record.id})}
data-confirm="Are you sure you want to import this record?"
>
<div class="flex min-w-0 gap-x-4">
<div class="min-w-0 flex-auto">
<p class="text-sm font-semibold leading-6 text-gray-900">
<%= @record.title %>
<span class="mt-1 text-xs leading-5 text-gray-500">
<%= @record.year %>
</span>
</p>
<p class="mt-1 truncate text-xs leading-5 text-gray-500"><%= @record.artists %></p>
</div>
</div>
<div class="hidden shrink-0 sm:flex sm:flex-col sm:items-end">
<.type_badge type={@record.type} />
</div>
</li>
"""
end
attr :type, :string, required: true
defp type_badge(assigns) do
~H"""
<span class="inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10">
<%= @type %>
</span>
"""
end
@impl true
def mount(socket) do
{:ok,
socket
|> assign(:records, [])
|> assign(:form, to_form(%{"query" => ""}))}
end
@impl true
def handle_event("search", %{"query" => query}, socket) do
{:ok, records} = search(query)
{:noreply,
socket
|> assign(:records, records)
|> assign(:form, to_form(%{"query" => query}))}
end
defp search(""), do: {:ok, []}
defp search(query) do
case MusicBrainz.search_release_group(query) do
{:ok, result} ->
{:ok,
Enum.map(result["release-groups"], fn rg ->
%{
id: rg["id"],
type: parse_subtype(rg["primary-type"]),
title: rg["title"],
artists:
rg["artist-credit"]
|> Enum.map(fn ac -> ac["artist"]["name"] end)
|> Enum.join(", "),
year: parse_year(rg["first-release-date"])
}
end)}
error ->
error
end
end
defp parse_year(iso_date) do
case Date.from_iso8601(iso_date) do
{:ok, date} -> date.year
_error -> nil
end
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
end
+1
View File
@@ -22,6 +22,7 @@ defmodule MusicLibraryWeb.Router do
live "/records", RecordLive.Index, :index
live "/records/new", RecordLive.Index, :new
live "/records/search", RecordLive.Index, :search
live "/records/:id/edit", RecordLive.Index, :edit
live "/records/:id", RecordLive.Show, :show