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
@moduledoc """
LiveBeats keeps the contexts that define your domain
and business logic.
require Logger
Contexts are also responsible for managing your data, regardless
if it comes from the database, an external API or others.
def attach(target_mod, opts) when is_atom(target_mod) do
{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

View file

@ -70,9 +70,8 @@ defmodule LiveBeats.Accounts do
set: [active_profile_user_id: profile_uid]
)
Phoenix.PubSub.broadcast!(
@pubsub,
topic(current_user.id),
broadcast!(
current_user,
%Events.ActiveProfileChanged{current_user: current_user, new_profile_user_id: profile_uid}
)
@ -114,6 +113,14 @@ defmodule LiveBeats.Accounts do
user
|> change_settings(attrs)
|> Repo.update()
|> case do
{:ok, new_user} ->
LiveBeats.execute(__MODULE__, %Events.PublicSettingsChanged{user: new_user})
{:ok, new_user}
{:error, _} = error ->
error
end
end
defp update_github_token(%User{} = user, new_token) do
@ -128,4 +135,8 @@ defmodule LiveBeats.Accounts do
{:ok, Repo.preload(user, :identities, force: true)}
end
defp broadcast!(%User{} = user, msg) do
Phoenix.PubSub.broadcast!(@pubsub, topic(user.id), {__MODULE__, msg})
end
end

View file

@ -2,4 +2,8 @@ defmodule LiveBeats.Accounts.Events do
defmodule ActiveProfileChanged do
defstruct current_user: nil, new_profile_user_id: nil
end
defmodule PublicSettingsChanged do
defstruct user: nil
end
end

View file

@ -50,8 +50,8 @@ defmodule LiveBeats.Accounts.User do
def settings_changeset(%User{} = user, params) do
user
|> cast(params, [:username])
|> validate_required([:username])
|> cast(params, [:username, :profile_tagline])
|> validate_required([:username, :profile_tagline])
|> validate_username()
end

View file

@ -7,6 +7,8 @@ defmodule LiveBeats.Application do
@impl true
def start(_type, _args) do
LiveBeats.MediaLibrary.attach()
children = [
{Task.Supervisor, name: LiveBeats.TaskSupervisor},
# Start the Ecto repository

View file

@ -16,6 +16,15 @@ defmodule LiveBeats.MediaLibrary do
defdelegate playing?(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
Phoenix.PubSub.subscribe(@pubsub, topic(profile.user_id))
end
@ -71,10 +80,7 @@ defmodule LiveBeats.MediaLibrary do
elapsed = elapsed_playback(new_song)
Phoenix.PubSub.broadcast!(@pubsub, topic(song.user_id), %Events.Play{
song: song,
elapsed: elapsed
})
broadcast!(song.user_id, %Events.Play{song: song, elapsed: elapsed})
new_song
end
@ -95,7 +101,7 @@ defmodule LiveBeats.MediaLibrary do
|> Multi.update_all(:now_paused, fn _ -> pause_query end, [])
|> Repo.transaction()
Phoenix.PubSub.broadcast!(@pubsub, topic(song.user_id), %Events.Pause{song: song})
broadcast!(song.user_id, %Events.Pause{song: song})
end
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
from(s in query, order_by: [{^direction, s.inserted_at}, {^direction, s.id}])
end
defp broadcast!(user_id, msg) when is_integer(user_id) do
Phoenix.PubSub.broadcast!(@pubsub, topic(user_id), {__MODULE__, msg})
end
end

View file

@ -6,4 +6,8 @@ defmodule LiveBeats.MediaLibrary.Events do
defmodule Pause do
defstruct song: nil
end
defmodule PublicProfileUpdated do
defstruct profile: nil
end
end

View file

@ -154,7 +154,9 @@ defmodule LiveBeatsWeb.PlayerLive do
when is_struct(profile, MediaLibrary.Profile) or is_nil(profile) do
%{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)
profile && MediaLibrary.subscribe_to_profile(profile)
end
@ -215,7 +217,11 @@ defmodule LiveBeatsWeb.PlayerLive do
{:noreply, socket}
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
{:noreply, assign(socket, profile: get_profile(user_id))}
else
@ -223,18 +229,20 @@ defmodule LiveBeatsWeb.PlayerLive do
end
end
def handle_info(:play_current, socket) do
{:noreply, play_current_song(socket)}
def handle_info({MediaLibrary, %MediaLibrary.Events.PublicProfileUpdated{} = update}, socket) do
{:noreply, assign_profile(socket, update.profile)}
end
def handle_info(%MediaLibrary.Events.Pause{}, socket) do
def handle_info({MediaLibrary, %MediaLibrary.Events.Pause{}}, socket) do
{:noreply, push_pause(socket)}
end
def handle_info(%MediaLibrary.Events.Play{song: song, elapsed: elapsed}, socket) do
{:noreply, play_song(socket, song, elapsed)}
def handle_info({MediaLibrary, %MediaLibrary.Events.Play{} = play}, socket) do
{:noreply, play_song(socket, play.song, play.elapsed)}
end
def handle_info({MediaLibrary, _}, socket), do: {:noreply, socket}
defp play_song(socket, %Song{} = song, elapsed) do
socket
|> push_play(song, elapsed)
@ -311,4 +319,11 @@ defmodule LiveBeatsWeb.PlayerLive do
defp get_profile(user_id) do
user_id && Accounts.get_user!(user_id) |> MediaLibrary.get_profile!()
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

View file

@ -125,18 +125,29 @@ defmodule LiveBeatsWeb.SongLive.Index do
{:noreply, socket}
end
def handle_info(%Accounts.Events.ActiveProfileChanged{new_profile_user_id: user_id}, socket) do
{:noreply, assign(socket, active_profile_id: user_id)}
def handle_info({Accounts, %Accounts.Events.ActiveProfileChanged{} = event}, socket) do
{:noreply, assign(socket, active_profile_id: event.new_profile_user_id)}
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)}
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)}
end
def handle_info({MediaLibrary, _}, socket), do: {:noreply, socket}
def handle_info({Accounts, _}, socket), do: {:noreply, socket}
defp stop_song(socket, song_id) do
SongRowComponent.send_status(song_id, :stopped)

View file

@ -22,10 +22,14 @@
alt="Workflow">
</div>
<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}/>
<% end %>
<nav class="px-2">
<%= if @current_user do %>
<.sidebar_nav_links current_user={@current_user}/>
<% end %>
<.sidebar_active_users id="desktop-active-users" users={@active_users}/>
</nav>
</div>
@ -46,7 +50,9 @@
</div>
<!-- Sidebar component, swap this element with another sidebar if you like -->
<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}/>
<% end %>
<!-- Sidebar Search -->
<div class="px-3 mt-5">
<label for="search" class="sr-only">Search</label>
@ -66,7 +72,9 @@
</div>
<!-- Navigation -->
<nav class="px-3 mt-6">
<%= if @current_user do %>
<.sidebar_nav_links current_user={@current_user}/>
<% end %>
<!-- Secondary navigation -->
<.sidebar_active_users id="mobile-active-users" users={@active_users}/>
</nav>

View file

@ -97,13 +97,12 @@ defmodule LiveBeatsWeb.LayoutView do
redirect_to={profile_path(@current_user)}
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
>View Profile</.link>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem">Settings</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem">Notifications</a>
</div>
<div class="py-1" role="none">
<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>
<.link
redirect_to={Routes.settings_path(LiveBeatsWeb.Endpoint, :edit)}
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem"
>Settings</.link>
</div>
<div class="py-1" role="none">
<.link
href={Routes.o_auth_callback_path(LiveBeatsWeb.Endpoint, :sign_out)}