From e873619a66d7efd5b472f65614adc744d6291e0e Mon Sep 17 00:00:00 2001 From: Chris McCord Date: Fri, 12 Nov 2021 12:41:16 -0500 Subject: [PATCH] Add event dispatch system with more profile updates --- lib/live_beats.ex | 34 +++++++++++++++---- lib/live_beats/accounts.ex | 17 ++++++++-- lib/live_beats/accounts/events.ex | 4 +++ lib/live_beats/accounts/user.ex | 4 +-- lib/live_beats/application.ex | 2 ++ lib/live_beats/media_library.ex | 20 ++++++++--- lib/live_beats/media_library/events.ex | 4 +++ lib/live_beats_web/live/player_live.ex | 29 ++++++++++++---- lib/live_beats_web/live/song_live/index.ex | 19 ++++++++--- .../templates/layout/live.html.heex | 16 ++++++--- lib/live_beats_web/views/layout_view.ex | 11 +++--- 11 files changed, 123 insertions(+), 37 deletions(-) diff --git a/lib/live_beats.ex b/lib/live_beats.ex index 50035f0..d790a61 100644 --- a/lib/live_beats.ex +++ b/lib/live_beats.ex @@ -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 diff --git a/lib/live_beats/accounts.ex b/lib/live_beats/accounts.ex index ab84bf2..c2c6a24 100644 --- a/lib/live_beats/accounts.ex +++ b/lib/live_beats/accounts.ex @@ -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 diff --git a/lib/live_beats/accounts/events.ex b/lib/live_beats/accounts/events.ex index 1f6d152..9f3980c 100644 --- a/lib/live_beats/accounts/events.ex +++ b/lib/live_beats/accounts/events.ex @@ -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 diff --git a/lib/live_beats/accounts/user.ex b/lib/live_beats/accounts/user.ex index 456f83e..b61caa9 100644 --- a/lib/live_beats/accounts/user.ex +++ b/lib/live_beats/accounts/user.ex @@ -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 diff --git a/lib/live_beats/application.ex b/lib/live_beats/application.ex index cf7dd3e..e40f2a0 100644 --- a/lib/live_beats/application.ex +++ b/lib/live_beats/application.ex @@ -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 diff --git a/lib/live_beats/media_library.ex b/lib/live_beats/media_library.ex index 37e098a..cc3458d 100644 --- a/lib/live_beats/media_library.ex +++ b/lib/live_beats/media_library.ex @@ -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 diff --git a/lib/live_beats/media_library/events.ex b/lib/live_beats/media_library/events.ex index 78f7d71..2831ed2 100644 --- a/lib/live_beats/media_library/events.ex +++ b/lib/live_beats/media_library/events.ex @@ -6,4 +6,8 @@ defmodule LiveBeats.MediaLibrary.Events do defmodule Pause do defstruct song: nil end + + defmodule PublicProfileUpdated do + defstruct profile: nil + end end diff --git a/lib/live_beats_web/live/player_live.ex b/lib/live_beats_web/live/player_live.ex index a5d5291..91830fa 100644 --- a/lib/live_beats_web/live/player_live.ex +++ b/lib/live_beats_web/live/player_live.ex @@ -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 diff --git a/lib/live_beats_web/live/song_live/index.ex b/lib/live_beats_web/live/song_live/index.ex index 27d8d0c..d6c59f6 100644 --- a/lib/live_beats_web/live/song_live/index.ex +++ b/lib/live_beats_web/live/song_live/index.ex @@ -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) diff --git a/lib/live_beats_web/templates/layout/live.html.heex b/lib/live_beats_web/templates/layout/live.html.heex index d602b08..2f8152d 100644 --- a/lib/live_beats_web/templates/layout/live.html.heex +++ b/lib/live_beats_web/templates/layout/live.html.heex @@ -22,10 +22,14 @@ alt="Workflow"> @@ -46,7 +50,9 @@ -
- Get desktop app - Support + <.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 href={Routes.o_auth_callback_path(LiveBeatsWeb.Endpoint, :sign_out)}