mirror of
https://github.com/fly-apps/live_beats.git
synced 2024-11-22 08:01:00 +00:00
Add event dispatch system with more profile updates
This commit is contained in:
parent
5ca7357665
commit
e873619a66
11 changed files with 123 additions and 37 deletions
|
@ -1,9 +1,31 @@
|
||||||
defmodule LiveBeats do
|
defmodule LiveBeats do
|
||||||
@moduledoc """
|
require Logger
|
||||||
LiveBeats keeps the contexts that define your domain
|
|
||||||
and business logic.
|
|
||||||
|
|
||||||
Contexts are also responsible for managing your data, regardless
|
def attach(target_mod, opts) when is_atom(target_mod) do
|
||||||
if it comes from the database, an external API or others.
|
{src_mod, struct_mod} = Keyword.fetch!(opts, :to)
|
||||||
|
_ = struct_mod.__struct__
|
||||||
|
|
||||||
|
:ok =
|
||||||
|
:telemetry.attach(target_mod, [src_mod, struct_mod], &__MODULE__.handle_execute/4, %{
|
||||||
|
target: target_mod
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute(src_mod, event_struct) when is_struct(event_struct) do
|
||||||
|
:telemetry.execute([src_mod, event_struct.__struct__], event_struct, %{})
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def handle_execute([src_mod, event_mod], %event_mod{} = event_struct, _meta, %{target: target}) do
|
||||||
|
try do
|
||||||
|
target.handle_execute({src_mod, event_struct})
|
||||||
|
catch
|
||||||
|
kind, err ->
|
||||||
|
Logger.error """
|
||||||
|
executing {#{inspect(src_mod)}, #{inspect(event_mod)}} failed with #{inspect(kind)}
|
||||||
|
|
||||||
|
#{inspect(err)}
|
||||||
"""
|
"""
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -70,9 +70,8 @@ defmodule LiveBeats.Accounts do
|
||||||
set: [active_profile_user_id: profile_uid]
|
set: [active_profile_user_id: profile_uid]
|
||||||
)
|
)
|
||||||
|
|
||||||
Phoenix.PubSub.broadcast!(
|
broadcast!(
|
||||||
@pubsub,
|
current_user,
|
||||||
topic(current_user.id),
|
|
||||||
%Events.ActiveProfileChanged{current_user: current_user, new_profile_user_id: profile_uid}
|
%Events.ActiveProfileChanged{current_user: current_user, new_profile_user_id: profile_uid}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -114,6 +113,14 @@ defmodule LiveBeats.Accounts do
|
||||||
user
|
user
|
||||||
|> change_settings(attrs)
|
|> change_settings(attrs)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
|
|> case do
|
||||||
|
{:ok, new_user} ->
|
||||||
|
LiveBeats.execute(__MODULE__, %Events.PublicSettingsChanged{user: new_user})
|
||||||
|
{:ok, new_user}
|
||||||
|
|
||||||
|
{:error, _} = error ->
|
||||||
|
error
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp update_github_token(%User{} = user, new_token) do
|
defp update_github_token(%User{} = user, new_token) do
|
||||||
|
@ -128,4 +135,8 @@ defmodule LiveBeats.Accounts do
|
||||||
|
|
||||||
{:ok, Repo.preload(user, :identities, force: true)}
|
{:ok, Repo.preload(user, :identities, force: true)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp broadcast!(%User{} = user, msg) do
|
||||||
|
Phoenix.PubSub.broadcast!(@pubsub, topic(user.id), {__MODULE__, msg})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,4 +2,8 @@ defmodule LiveBeats.Accounts.Events do
|
||||||
defmodule ActiveProfileChanged do
|
defmodule ActiveProfileChanged do
|
||||||
defstruct current_user: nil, new_profile_user_id: nil
|
defstruct current_user: nil, new_profile_user_id: nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defmodule PublicSettingsChanged do
|
||||||
|
defstruct user: nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -50,8 +50,8 @@ defmodule LiveBeats.Accounts.User do
|
||||||
|
|
||||||
def settings_changeset(%User{} = user, params) do
|
def settings_changeset(%User{} = user, params) do
|
||||||
user
|
user
|
||||||
|> cast(params, [:username])
|
|> cast(params, [:username, :profile_tagline])
|
||||||
|> validate_required([:username])
|
|> validate_required([:username, :profile_tagline])
|
||||||
|> validate_username()
|
|> validate_username()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@ defmodule LiveBeats.Application do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def start(_type, _args) do
|
def start(_type, _args) do
|
||||||
|
LiveBeats.MediaLibrary.attach()
|
||||||
|
|
||||||
children = [
|
children = [
|
||||||
{Task.Supervisor, name: LiveBeats.TaskSupervisor},
|
{Task.Supervisor, name: LiveBeats.TaskSupervisor},
|
||||||
# Start the Ecto repository
|
# Start the Ecto repository
|
||||||
|
|
|
@ -16,6 +16,15 @@ defmodule LiveBeats.MediaLibrary do
|
||||||
defdelegate playing?(song), to: Song
|
defdelegate playing?(song), to: Song
|
||||||
defdelegate paused?(song), to: Song
|
defdelegate paused?(song), to: Song
|
||||||
|
|
||||||
|
def attach do
|
||||||
|
LiveBeats.attach(LiveBeats.MediaLibrary, to: {Accounts, Accounts.Events.PublicSettingsChanged})
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_execute({Accounts, %Accounts.Events.PublicSettingsChanged{user: user}}) do
|
||||||
|
profile = get_profile!(user)
|
||||||
|
broadcast!(user.id, %Events.PublicProfileUpdated{profile: profile})
|
||||||
|
end
|
||||||
|
|
||||||
def subscribe_to_profile(%Profile{} = profile) do
|
def subscribe_to_profile(%Profile{} = profile) do
|
||||||
Phoenix.PubSub.subscribe(@pubsub, topic(profile.user_id))
|
Phoenix.PubSub.subscribe(@pubsub, topic(profile.user_id))
|
||||||
end
|
end
|
||||||
|
@ -71,10 +80,7 @@ defmodule LiveBeats.MediaLibrary do
|
||||||
|
|
||||||
elapsed = elapsed_playback(new_song)
|
elapsed = elapsed_playback(new_song)
|
||||||
|
|
||||||
Phoenix.PubSub.broadcast!(@pubsub, topic(song.user_id), %Events.Play{
|
broadcast!(song.user_id, %Events.Play{song: song, elapsed: elapsed})
|
||||||
song: song,
|
|
||||||
elapsed: elapsed
|
|
||||||
})
|
|
||||||
|
|
||||||
new_song
|
new_song
|
||||||
end
|
end
|
||||||
|
@ -95,7 +101,7 @@ defmodule LiveBeats.MediaLibrary do
|
||||||
|> Multi.update_all(:now_paused, fn _ -> pause_query end, [])
|
|> Multi.update_all(:now_paused, fn _ -> pause_query end, [])
|
||||||
|> Repo.transaction()
|
|> Repo.transaction()
|
||||||
|
|
||||||
Phoenix.PubSub.broadcast!(@pubsub, topic(song.user_id), %Events.Pause{song: song})
|
broadcast!(song.user_id, %Events.Pause{song: song})
|
||||||
end
|
end
|
||||||
|
|
||||||
def play_next_song_auto(%Profile{} = profile) do
|
def play_next_song_auto(%Profile{} = profile) do
|
||||||
|
@ -321,4 +327,8 @@ defmodule LiveBeats.MediaLibrary do
|
||||||
defp order_by_playlist(%Ecto.Query{} = query, direction) when direction in [:asc, :desc] do
|
defp order_by_playlist(%Ecto.Query{} = query, direction) when direction in [:asc, :desc] do
|
||||||
from(s in query, order_by: [{^direction, s.inserted_at}, {^direction, s.id}])
|
from(s in query, order_by: [{^direction, s.inserted_at}, {^direction, s.id}])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp broadcast!(user_id, msg) when is_integer(user_id) do
|
||||||
|
Phoenix.PubSub.broadcast!(@pubsub, topic(user_id), {__MODULE__, msg})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,4 +6,8 @@ defmodule LiveBeats.MediaLibrary.Events do
|
||||||
defmodule Pause do
|
defmodule Pause do
|
||||||
defstruct song: nil
|
defstruct song: nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defmodule PublicProfileUpdated do
|
||||||
|
defstruct profile: nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -154,7 +154,9 @@ defmodule LiveBeatsWeb.PlayerLive do
|
||||||
when is_struct(profile, MediaLibrary.Profile) or is_nil(profile) do
|
when is_struct(profile, MediaLibrary.Profile) or is_nil(profile) do
|
||||||
%{profile: prev_profile, current_user: current_user} = socket.assigns
|
%{profile: prev_profile, current_user: current_user} = socket.assigns
|
||||||
|
|
||||||
if connected?(socket) do
|
profile_changed? = profile_changed?(prev_profile, profile)
|
||||||
|
|
||||||
|
if connected?(socket) and profile_changed? do
|
||||||
prev_profile && MediaLibrary.unsubscribe_to_profile(prev_profile)
|
prev_profile && MediaLibrary.unsubscribe_to_profile(prev_profile)
|
||||||
profile && MediaLibrary.subscribe_to_profile(profile)
|
profile && MediaLibrary.subscribe_to_profile(profile)
|
||||||
end
|
end
|
||||||
|
@ -215,7 +217,11 @@ defmodule LiveBeatsWeb.PlayerLive do
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info(%Accounts.Events.ActiveProfileChanged{new_profile_user_id: user_id}, socket) do
|
def handle_info(:play_current, socket) do
|
||||||
|
{:noreply, play_current_song(socket)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info({Accounts, %Accounts.Events.ActiveProfileChanged{new_profile_user_id: user_id}}, socket) do
|
||||||
if user_id do
|
if user_id do
|
||||||
{:noreply, assign(socket, profile: get_profile(user_id))}
|
{:noreply, assign(socket, profile: get_profile(user_id))}
|
||||||
else
|
else
|
||||||
|
@ -223,18 +229,20 @@ defmodule LiveBeatsWeb.PlayerLive do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info(:play_current, socket) do
|
def handle_info({MediaLibrary, %MediaLibrary.Events.PublicProfileUpdated{} = update}, socket) do
|
||||||
{:noreply, play_current_song(socket)}
|
{:noreply, assign_profile(socket, update.profile)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info(%MediaLibrary.Events.Pause{}, socket) do
|
def handle_info({MediaLibrary, %MediaLibrary.Events.Pause{}}, socket) do
|
||||||
{:noreply, push_pause(socket)}
|
{:noreply, push_pause(socket)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info(%MediaLibrary.Events.Play{song: song, elapsed: elapsed}, socket) do
|
def handle_info({MediaLibrary, %MediaLibrary.Events.Play{} = play}, socket) do
|
||||||
{:noreply, play_song(socket, song, elapsed)}
|
{:noreply, play_song(socket, play.song, play.elapsed)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_info({MediaLibrary, _}, socket), do: {:noreply, socket}
|
||||||
|
|
||||||
defp play_song(socket, %Song{} = song, elapsed) do
|
defp play_song(socket, %Song{} = song, elapsed) do
|
||||||
socket
|
socket
|
||||||
|> push_play(song, elapsed)
|
|> push_play(song, elapsed)
|
||||||
|
@ -311,4 +319,11 @@ defmodule LiveBeatsWeb.PlayerLive do
|
||||||
defp get_profile(user_id) do
|
defp get_profile(user_id) do
|
||||||
user_id && Accounts.get_user!(user_id) |> MediaLibrary.get_profile!()
|
user_id && Accounts.get_user!(user_id) |> MediaLibrary.get_profile!()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp profile_changed?(nil = _prev_profile, nil = _new_profile), do: false
|
||||||
|
defp profile_changed?(nil = _prev_profile, %MediaLibrary.Profile{}), do: true
|
||||||
|
defp profile_changed?(%MediaLibrary.Profile{}, nil = _new_profile), do: true
|
||||||
|
|
||||||
|
defp profile_changed?(%MediaLibrary.Profile{} = prev, %MediaLibrary.Profile{} = new),
|
||||||
|
do: prev.user_id != new.user_id
|
||||||
end
|
end
|
||||||
|
|
|
@ -125,18 +125,29 @@ defmodule LiveBeatsWeb.SongLive.Index do
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info(%Accounts.Events.ActiveProfileChanged{new_profile_user_id: user_id}, socket) do
|
def handle_info({Accounts, %Accounts.Events.ActiveProfileChanged{} = event}, socket) do
|
||||||
{:noreply, assign(socket, active_profile_id: user_id)}
|
{:noreply, assign(socket, active_profile_id: event.new_profile_user_id)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info(%MediaLibrary.Events.Play{song: song}, socket) do
|
def handle_info({MediaLibrary, %MediaLibrary.Events.PublicProfileUpdated{} = update}, socket) do
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(profile: update.profile)
|
||||||
|
|> push_patch(to: profile_path(update.profile))}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info({MediaLibrary, %MediaLibrary.Events.Play{song: song}}, socket) do
|
||||||
{:noreply, play_song(socket, song)}
|
{:noreply, play_song(socket, song)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info(%MediaLibrary.Events.Pause{song: song}, socket) do
|
def handle_info({MediaLibrary, %MediaLibrary.Events.Pause{song: song}}, socket) do
|
||||||
{:noreply, pause_song(socket, song.id)}
|
{:noreply, pause_song(socket, song.id)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_info({MediaLibrary, _}, socket), do: {:noreply, socket}
|
||||||
|
|
||||||
|
def handle_info({Accounts, _}, socket), do: {:noreply, socket}
|
||||||
|
|
||||||
defp stop_song(socket, song_id) do
|
defp stop_song(socket, song_id) do
|
||||||
SongRowComponent.send_status(song_id, :stopped)
|
SongRowComponent.send_status(song_id, :stopped)
|
||||||
|
|
||||||
|
|
|
@ -22,10 +22,14 @@
|
||||||
alt="Workflow">
|
alt="Workflow">
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-5 flex-1 h-0 overflow-y-auto">
|
<div class="mt-5 flex-1 h-0 overflow-y-auto">
|
||||||
|
<%= if @current_user do %>
|
||||||
<.sidebar_account_dropdown id="mobile-account-dropdown" current_user={@current_user}/>
|
<.sidebar_account_dropdown id="mobile-account-dropdown" current_user={@current_user}/>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<nav class="px-2">
|
<nav class="px-2">
|
||||||
|
<%= if @current_user do %>
|
||||||
<.sidebar_nav_links current_user={@current_user}/>
|
<.sidebar_nav_links current_user={@current_user}/>
|
||||||
|
<% end %>
|
||||||
<.sidebar_active_users id="desktop-active-users" users={@active_users}/>
|
<.sidebar_active_users id="desktop-active-users" users={@active_users}/>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,7 +50,9 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- Sidebar component, swap this element with another sidebar if you like -->
|
<!-- Sidebar component, swap this element with another sidebar if you like -->
|
||||||
<div class="h-0 flex-1 flex flex-col overflow-y-auto">
|
<div class="h-0 flex-1 flex flex-col overflow-y-auto">
|
||||||
|
<%= if @current_user do %>
|
||||||
<.sidebar_account_dropdown id="account-dropdown" current_user={@current_user}/>
|
<.sidebar_account_dropdown id="account-dropdown" current_user={@current_user}/>
|
||||||
|
<% end %>
|
||||||
<!-- Sidebar Search -->
|
<!-- Sidebar Search -->
|
||||||
<div class="px-3 mt-5">
|
<div class="px-3 mt-5">
|
||||||
<label for="search" class="sr-only">Search</label>
|
<label for="search" class="sr-only">Search</label>
|
||||||
|
@ -66,7 +72,9 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- Navigation -->
|
<!-- Navigation -->
|
||||||
<nav class="px-3 mt-6">
|
<nav class="px-3 mt-6">
|
||||||
|
<%= if @current_user do %>
|
||||||
<.sidebar_nav_links current_user={@current_user}/>
|
<.sidebar_nav_links current_user={@current_user}/>
|
||||||
|
<% end %>
|
||||||
<!-- Secondary navigation -->
|
<!-- Secondary navigation -->
|
||||||
<.sidebar_active_users id="mobile-active-users" users={@active_users}/>
|
<.sidebar_active_users id="mobile-active-users" users={@active_users}/>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
@ -97,13 +97,12 @@ defmodule LiveBeatsWeb.LayoutView do
|
||||||
redirect_to={profile_path(@current_user)}
|
redirect_to={profile_path(@current_user)}
|
||||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
||||||
>View Profile</.link>
|
>View Profile</.link>
|
||||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem">Settings</a>
|
<.link
|
||||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem">Notifications</a>
|
redirect_to={Routes.settings_path(LiveBeatsWeb.Endpoint, :edit)}
|
||||||
</div>
|
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem"
|
||||||
<div class="py-1" role="none">
|
>Settings</.link>
|
||||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem">Get desktop app</a>
|
|
||||||
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem">Support</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="py-1" role="none">
|
<div class="py-1" role="none">
|
||||||
<.link
|
<.link
|
||||||
href={Routes.o_auth_callback_path(LiveBeatsWeb.Endpoint, :sign_out)}
|
href={Routes.o_auth_callback_path(LiveBeatsWeb.Endpoint, :sign_out)}
|
||||||
|
|
Loading…
Reference in a new issue