Add event dispatch system with more profile updates

This commit is contained in:
Chris McCord 2021-11-12 12:41:16 -05:00
parent 5ca7357665
commit e873619a66
11 changed files with 123 additions and 37 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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">
<.sidebar_account_dropdown id="mobile-account-dropdown" current_user={@current_user}/> <%= if @current_user do %>
<.sidebar_account_dropdown id="mobile-account-dropdown" current_user={@current_user}/>
<% end %>
<nav class="px-2"> <nav class="px-2">
<.sidebar_nav_links current_user={@current_user}/> <%= if @current_user do %>
<.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">
<.sidebar_account_dropdown id="account-dropdown" current_user={@current_user}/> <%= if @current_user do %>
<.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">
<.sidebar_nav_links current_user={@current_user}/> <%= if @current_user do %>
<.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>

View file

@ -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)}