Skip to content

Commit

Permalink
Load spotify player on mandolin recommendations
Browse files Browse the repository at this point in the history
  • Loading branch information
shedrachokonofua committed Jul 13, 2024
1 parent 784111e commit f45bde5
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 51 deletions.
20 changes: 6 additions & 14 deletions connector/mandolin/README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
# Mandolin

To start your Phoenix server:
Mandolin is a tiny web-app for getting album recommendations on rateyourmusic lists.

* Run `mix setup` to install and setup dependencies
* Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server`
## How it works:

Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.

Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html).

## Learn more

* Official website: https://www.phoenixframework.org/
* Guides: https://hexdocs.pm/phoenix/overview.html
* Docs: https://hexdocs.pm/phoenix
* Forum: https://elixirforum.com/c/phoenix-forum
* Source: https://github.com/phoenixframework/phoenix
1. User enters a list URL
2. Mandolin starts a list lookup on lute
3. When lute completes the lookup, Mandolin creates a Lute profile and populates it with the list data
4. Mandolin serves a recommendation page based on the list profile
16 changes: 6 additions & 10 deletions connector/mandolin/lib/mandolin/list_profile.ex
Original file line number Diff line number Diff line change
Expand Up @@ -62,22 +62,18 @@ defmodule Mandolin.ListProfile do
end

defp populate_profile(profile, lookup) do
populated =
missing_albums =
lookup.segments
|> Enum.flat_map(& &1.album_file_names)
|> MapSet.new()
|> Enum.all?(&Map.has_key?(profile.albums, &1))
|> Enum.reject(&Map.has_key?(profile.albums, &1))

if populated do
fully_populated = Enum.empty?(missing_albums)

if fully_populated do
{:ok, profile}
else
input =
Enum.flat_map(lookup.segments, fn segment ->
Enum.map(segment.album_file_names, fn file_name ->
%{file_name: file_name, factor: 1}
end)
end)

input = Enum.map(missing_albums, fn file_name -> %{file_name: file_name, factor: 1} end)
Logger.info("Populating profile #{profile.id} with #{length(input)} albums")
Mandolin.Lute.Client.put_many_albums_on_profile(profile.id, input)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -598,17 +598,13 @@ defmodule MandolinWeb.CoreComponents do
"""
end

attr :size, :integer, default: 12

def spinner(assigns) do
~H"""
<div role="status">
<svg
aria-hidden="true"
class={[
"w-" <> to_string(@size),
"h-" <> to_string(@size),
"text-gray-200 animate-spin dark:text-gray-600 fill-brand"
"w-12 h-12 text-gray-200 animate-spin dark:text-gray-600 fill-brand"
]}
viewBox="0 0 100 101"
fill="none"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,33 @@
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
</script>
<script src="https://open.spotify.com/embed/iframe-api/v1" async>
</script>
</head>
<body class="bg-white">
<%= @inner_content %>

<script>
const loadPlayers = (IFrameAPI) => {
const targets = document.querySelectorAll(".spotify-player");
console.log(targets);
for (const target of targets) {
if (!target.dataset.spotifyid) {
continue;
}
console.log(target.dataset.spotifyid);
const options = {
width: '100%',
height: '80',
uri: `spotify:album:${target.dataset.spotifyid}`,
};
IFrameAPI.createController(target, options, () => {});
}
};
window.onSpotifyIframeApiReady = (IFrameAPI) => {
setTimeout(() => loadPlayers(IFrameAPI), 3000);
};
</script>
</body>
</html>
59 changes: 38 additions & 21 deletions connector/mandolin/lib/mandolin_web/live/list_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ defmodule MandolinWeb.ListLive do
<:failed :let={{:error, error}}>
<div>
<%= case error do %>
<% [status: status, progress: progress] when status in [:Started, :InProgress] -> %>
<% [lookup_status: status, progress: progress] when status in [:Started, :InProgress] -> %>
<.in_progress_message progress={progress} />
<% _ -> %>
<.failure_message error={error} />
Expand Down Expand Up @@ -108,10 +108,10 @@ defmodule MandolinWeb.ListLive do
defp recommendations_tab(assigns) do
~H"""
<div class="grid grid-cols-1 gap-4 lg:grid-cols-3 lg:gap-8">
<div>
<article class="rounded-xl bg-gradient-to-r from-green-300 via-blue-500 to-purple-600 p-0.5 shadow-lg">
<div class="border-right border-gray-200 border-2">
<article class="rounded-xl p-0.5">
<.form for={%{}} phx-submit="update_recommendations">
<div class="rounded-[10px] bg-white p-4 flex flex-col gap-4">
<div class="p-4 flex flex-col gap-4">
<div class="flex items-center gap-2">
<.icon name="hero-cog-6-tooth" />
<h2 class="mt-0.5 font-semibold text-brand">
Expand Down Expand Up @@ -216,29 +216,42 @@ defmodule MandolinWeb.ListLive do
<.async_result :let={data} assign={@recommendation_data}>
<:loading>
<div class="flex flex-col gap-4">
<div :for={x <- 1..5} class="bg-brand/25 animate-pulse" style="height: 75px"></div>
<div :for={_ <- 1..5} class="bg-brand/25 animate-pulse" style="height: 75px"></div>
</div>
</:loading>
<:failed>
<div>Failed to load recommendations</div>
</:failed>
<div class="flex flex-col gap-4">
<div>
<div class="flex flex-col divide-y divide-slate-200 shadow-xs rounded-lg shadow-brand">
<div class="p-2">
<h2 class="font-semibold text-brand">
Recommendations
</h2>
</div>
<div :for={r <- data} class="flex gap-4">
<div :for={r <- data} class="flex gap-4 p-4">
<img
src={r.album.cover_image_url}
alt={r.album.name}
width="75"
style="min-height: 75px"
style="height: 75px"
id={"cover-" <> r.album.file_name}
phx-hook="AlbumCover"
data-filename={r.album.file_name}
/>
<%= r.album.name %>
<div>
<div class="font-medium text-brand">
<%= r.album.name %> (<%= r.album.release_date %>)
</div>
<div class="text-sm">
<%= Enum.map(r.album.artists, & &1.name) |> Enum.join(", ") %>
</div>
<div class="text-sm"><%= r.album.primary_genres |> Enum.join(", ") %></div>
<div>
<div class="w-3xl pt-4">
<div data-spotifyid={r.album.spotify_id} class="spotify-player"></div>
</div>
</div>
</div>
</div>
</div>
</.async_result>
Expand Down Expand Up @@ -286,19 +299,23 @@ defmodule MandolinWeb.ListLive do
end

socket =
case tab do
:recommendations ->
recommendation_settings = Recommendation.Settings.build(params)
if connected?(socket) do
case tab do
:recommendations ->
recommendation_settings = Recommendation.Settings.build(params)

socket
|> assign(:recommendation_settings, recommendation_settings)
|> assign_async(:recommendation_data, fn ->
fetch_recommendations(file_name, recommendation_settings)
end)
socket
|> assign(:recommendation_settings, recommendation_settings)
|> assign_async(:recommendation_data, fn ->
fetch_recommendations(file_name, recommendation_settings)
end)

_ ->
socket
|> assign(:recommendation_data, AsyncResult.loading())
_ ->
socket
|> assign(:recommendation_data, AsyncResult.loading())
end
else
socket
end

{:noreply,
Expand Down
7 changes: 7 additions & 0 deletions core/src/albums/redis_album_search_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,7 @@ impl AlbumSearchIndex for RedisAlbumSearchIndex {
FtSearchReturnAttribute::identifier("$.duplicate_of"),
FtSearchReturnAttribute::identifier("$.duplicates"),
FtSearchReturnAttribute::identifier("$.cover_image_url"),
FtSearchReturnAttribute::identifier("$.spotify_id"),
]),
)
.await?;
Expand Down Expand Up @@ -607,6 +608,12 @@ impl AlbumSearchIndex for RedisAlbumSearchIndex {
_ => album_builder.cover_image_url(Some(value)),
};
}
"$.spotify_id" => {
match value.as_str() {
"" => album_builder.spotify_id(None),
_ => album_builder.spotify_id(Some(value)),
};
}
_ => {}
};
}
Expand Down
1 change: 1 addition & 0 deletions core/src/profile/profile_interactor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ impl ProfileInteractor {
.await
}

#[instrument(skip(self), name = "ProfileInteractor::put_many_albums_on_profile")]
pub async fn put_many_albums_on_profile(
&self,
id: &ProfileId,
Expand Down
3 changes: 2 additions & 1 deletion core/src/profile/profile_repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use rustis::{
commands::{GenericCommands, JsonCommands, JsonGetOptions, SetCondition},
};
use std::sync::Arc;
use tracing::warn;
use tracing::{instrument, warn};

pub struct ProfileRepository {
pub redis_connection_pool: Arc<Pool<PooledClientManager>>,
Expand Down Expand Up @@ -118,6 +118,7 @@ impl ProfileRepository {
Ok(json.is_some() && json.unwrap() != "[]")
}

#[instrument(skip(self), name = "ProfileRepository::put_album_on_profile")]
pub async fn put_album_on_profile(
&self,
id: &ProfileId,
Expand Down

0 comments on commit f45bde5

Please sign in to comment.