diff --git a/lib/live_beats/accounts.ex b/lib/live_beats/accounts.ex index feb9b83..c063d2f 100644 --- a/lib/live_beats/accounts.ex +++ b/lib/live_beats/accounts.ex @@ -74,6 +74,16 @@ defmodule LiveBeats.Accounts do Repo.one(query) end + def change_settings(%User{} = user, attrs) do + User.settings_changeset(user, attrs) + end + + def update_settings(%User{} = user, attrs) do + user + |> change_settings(attrs) + |> Repo.update() + end + defp update_github_token(%User{} = user, new_token) do identity = Repo.one!(from(i in Identity, where: i.user_id == ^user.id and i.provider == "github")) diff --git a/lib/live_beats/accounts/user.ex b/lib/live_beats/accounts/user.ex index af572a3..b4c2a8c 100644 --- a/lib/live_beats/accounts/user.ex +++ b/lib/live_beats/accounts/user.ex @@ -10,6 +10,7 @@ defmodule LiveBeats.Accounts.User do field :username, :string field :confirmed_at, :naive_datetime field :role, :string, default: "subscriber" + field :profile_tagline, :string has_many :identities, Identity @@ -21,16 +22,21 @@ defmodule LiveBeats.Accounts.User do """ def github_registration_changeset(info, primary_email, emails, token) do %{"login" => username} = info - identity_changeset = Identity.github_registration_changeset(info, primary_email, emails, token) + + identity_changeset = + Identity.github_registration_changeset(info, primary_email, emails, token) + if identity_changeset.valid? do params = %{ "username" => username, "email" => primary_email, - "name" => get_change(identity_changeset, :provider_name), + "name" => get_change(identity_changeset, :provider_name) } + %User{} |> cast(params, [:email, :name, :username]) |> validate_required([:email, :name, :username]) + |> validate_username() |> validate_email() |> put_assoc(:identities, [identity_changeset]) else @@ -41,6 +47,13 @@ defmodule LiveBeats.Accounts.User do end end + def settings_changeset(%User{} = user, params) do + user + |> cast(params, [:username]) + |> validate_required([:username]) + |> validate_username() + end + defp validate_email(changeset) do changeset |> validate_required([:email]) @@ -49,4 +62,21 @@ defmodule LiveBeats.Accounts.User do |> unsafe_validate_unique(:email, LiveBeats.Repo) |> unique_constraint(:email) end + + defp validate_username(changeset) do + changeset + |> validate_format(:username, ~r/^[a-z0-9_-]{2,32}$/) + |> unsafe_validate_unique(:username, LiveBeats.Repo) + |> unique_constraint(:username) + |> prepare_changes(fn changeset -> + case fetch_change(changeset, :profile_tagline) do + {:ok, _} -> + changeset + + :error -> + username = get_field(changeset, :username) + put_change(changeset, :profile_tagline, "#{username}'s beats") + end + end) + end end diff --git a/lib/live_beats_web/controllers/redirect_controller.ex b/lib/live_beats_web/controllers/redirect_controller.ex new file mode 100644 index 0000000..fd42b28 --- /dev/null +++ b/lib/live_beats_web/controllers/redirect_controller.ex @@ -0,0 +1,11 @@ +defmodule LiveBeatsWeb.RedirectController do + use LiveBeatsWeb, :controller + + def redirect_authenticated(conn, _) do + if conn.assigns.current_user do + LiveBeatsWeb.UserAuth.redirect_if_user_is_authenticated(conn, []) + else + redirect(conn, to: Routes.sign_in_path(conn, :index)) + end + end +end diff --git a/lib/live_beats_web/controllers/user_auth.ex b/lib/live_beats_web/controllers/user_auth.ex index f170fa2..a4ccbd3 100644 --- a/lib/live_beats_web/controllers/user_auth.ex +++ b/lib/live_beats_web/controllers/user_auth.ex @@ -43,6 +43,7 @@ defmodule LiveBeatsWeb.UserAuth do """ def log_in_user(conn, user) do user_return_to = get_session(conn, :user_return_to) + conn = assign(conn, :current_user, user) conn |> renew_session() @@ -107,7 +108,7 @@ defmodule LiveBeatsWeb.UserAuth do conn |> put_flash(:error, "You must log in to access this page.") |> maybe_store_return_to() - |> redirect(to: Routes.home_path(conn, :index)) + |> redirect(to: Routes.sign_in_path(conn, :index)) |> halt() end end @@ -134,5 +135,5 @@ defmodule LiveBeatsWeb.UserAuth do defp maybe_store_return_to(conn), do: conn - defp signed_in_path(_conn), do: "/" + def signed_in_path(conn), do: Routes.song_index_path(conn, :index, conn.assigns.current_user.username) end diff --git a/lib/live_beats_web/live/live_helpers.ex b/lib/live_beats_web/live/live_helpers.ex index 115a2a8..4a7daff 100644 --- a/lib/live_beats_web/live/live_helpers.ex +++ b/lib/live_beats_web/live/live_helpers.ex @@ -2,8 +2,13 @@ defmodule LiveBeatsWeb.LiveHelpers do import Phoenix.LiveView import Phoenix.LiveView.Helpers + alias LiveBeatsWeb.Router.Helpers, as: Routes alias Phoenix.LiveView.JS + def home_path(socket) do + Routes.song_index_path(socket, :index, socket.assigns.current_user.username) + end + def spinner(assigns) do ~H""" diff --git a/lib/live_beats_web/live/settings_live.ex b/lib/live_beats_web/live/settings_live.ex new file mode 100644 index 0000000..aad6e5c --- /dev/null +++ b/lib/live_beats_web/live/settings_live.ex @@ -0,0 +1,93 @@ +defmodule LiveBeatsWeb.SettingsLive do + use LiveBeatsWeb, :live_view + + alias LiveBeats.Accounts + + def render(assigns) do + ~H""" + <.title_bar> + Profile Settings + + +
+ <.form let={f} for={@changeset} phx-change="validate" phx-submit="save" class="space-y-8 divide-y divide-gray-200"> +
+
+
+

+ This information will be displayed publicly so be careful what you share. +

+
+ +
+
+ +
+ + <%= URI.parse(LiveBeatsWeb.Endpoint.url()).host %>/ + + <%= text_input f, :username, class: "flex-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full min-w-0 rounded-none rounded-r-md sm:text-sm border-gray-300" %> + <.error field={:username} input_name="user[username]" errors={@changeset.errors} class="pt-2 pl-4 pr-4 ml-2 text-center" /> +
+
+ +
+ +
+ <%= text_input f, :email, disabled: true, class: "flex-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full min-w-0 rounded-md sm:text-sm border-gray-300 bg-gray-50" %> +
+
+ +
+ +
+ <%= text_input f, :profile_tagline, class: "flex-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full min-w-0 rounded-md sm:text-sm border-gray-300" %> + <.error field={:profile_tagline} input_name="user[profile_tagline]" errors={@changeset.errors} class="pt-2 pl-4 pr-4 ml-2 text-center" /> +
+

Write a short tagline for your beats page.

+
+
+
+
+ +
+
+ +
+
+ +
+ """ + end + + def mount(_parmas, _session, socket) do + changeset = Accounts.change_settings(socket.assigns.current_user, %{}) + {:ok, assign(socket, changeset: changeset)} + end + + def handle_event("validate", %{"user" => params}, socket) do + changeset = Accounts.change_settings(socket.assigns.current_user, params) + {:noreply, assign(socket, changeset: changeset)} + end + + def handle_event("save", %{"user" => params}, socket) do + case Accounts.update_settings(socket.assigns.current_user, params) do + {:ok, user} -> + {:noreply, + socket + |> assign(current_user: user) + |> put_flash(:info, "settings updated!")} + + {:error, changeset} -> + {:noreply, assign(socket, changeset: changeset)} + end + end +end diff --git a/lib/live_beats_web/live/song_live/index.ex b/lib/live_beats_web/live/song_live/index.ex index a7ddad8..f697c81 100644 --- a/lib/live_beats_web/live/song_live/index.ex +++ b/lib/live_beats_web/live/song_live/index.ex @@ -130,7 +130,7 @@ defmodule LiveBeatsWeb.SongLive.Index do LayoutComponent.show_modal(UploadFormComponent, %{ id: :new, confirm: {"Save", type: "submit", form: "song-form"}, - patch_to: Routes.song_index_path(socket, :index), + patch_to: home_path(socket), song: socket.assigns.song, title: socket.assigns.page_title, current_user: socket.assigns.current_user diff --git a/lib/live_beats_web/live/song_live/song_entry_component.ex b/lib/live_beats_web/live/song_live/song_entry_component.ex index 9dd5ce7..0d0b010 100644 --- a/lib/live_beats_web/live/song_live/song_entry_component.ex +++ b/lib/live_beats_web/live/song_live/song_entry_component.ex @@ -30,8 +30,8 @@ defmodule LiveBeatsWeb.SongLive.SongEntryComponent do class="block w-full border-0 p-0 text-gray-900 placeholder-gray-500 focus:ring-0 sm:text-sm"/>
- <.error input_name={"songs[#{@ref}][title]"} field={:title} errors={@errors}/> - <.error input_name={"songs[#{@ref}][artist]"} field={:artist} errors={@errors}/> + <.error input_name={"songs[#{@ref}][title]"} field={:title} errors={@errors} class="-mt-1"/> + <.error input_name={"songs[#{@ref}][artist]"} field={:artist} errors={@errors} class="-mt-1"/>
diff --git a/lib/live_beats_web/router.ex b/lib/live_beats_web/router.ex index 1039ad9..33469da 100644 --- a/lib/live_beats_web/router.ex +++ b/lib/live_beats_web/router.ex @@ -1,7 +1,8 @@ defmodule LiveBeatsWeb.Router do use LiveBeatsWeb, :router - import LiveBeatsWeb.UserAuth, only: [redirect_if_user_is_authenticated: 2] + import LiveBeatsWeb.UserAuth, + only: [fetch_current_user: 2, redirect_if_user_is_authenticated: 2] pipeline :browser do plug :accepts, ["html"] @@ -10,48 +11,19 @@ defmodule LiveBeatsWeb.Router do plug :put_root_layout, {LiveBeatsWeb.LayoutView, :root} plug :protect_from_forgery plug :put_secure_browser_headers + plug :fetch_current_user end pipeline :api do plug :accepts, ["json"] end - scope "/", LiveBeatsWeb do - pipe_through :browser - - get "/files/:id", FileController, :show - - delete "/signout", OAuthCallbackController, :sign_out - - live_session :default, on_mount: [{LiveBeatsWeb.UserAuth, :current_user}, LiveBeatsWeb.Nav] do - live "/signin", SignInLive, :index - end - - live_session :authenticated, on_mount: [{LiveBeatsWeb.UserAuth, :ensure_authenticated}, LiveBeatsWeb.Nav] do - live "/", HomeLive, :index - live "/songs", SongLive.Index, :index - live "/songs/new", SongLive.Index, :new - end - end - scope "/", LiveBeatsWeb do pipe_through [:browser, :redirect_if_user_is_authenticated] get "/oauth/callbacks/:provider", OAuthCallbackController, :new end - # Other scopes may use custom stacks. - # scope "/api", LiveBeatsWeb do - # pipe_through :api - # end - - # Enables LiveDashboard only for development - # - # If you want to use the LiveDashboard in production, you should put - # it behind authentication and allow only admins to access it. - # If your application does not have an admins-only section yet, - # you can use Plug.BasicAuth to set up some basic authentication - # as long as you are also using SSL (which you should anyway). if Mix.env() in [:dev, :test] do import Phoenix.LiveDashboard.Router @@ -61,10 +33,6 @@ defmodule LiveBeatsWeb.Router do end end - # Enables the Swoosh mailbox preview in development. - # - # Note that preview only shows emails that were sent by the same - # node running the Phoenix server. if Mix.env() == :dev do scope "/dev" do pipe_through :browser @@ -72,4 +40,24 @@ defmodule LiveBeatsWeb.Router do forward "/mailbox", Plug.Swoosh.MailboxPreview end end + + scope "/", LiveBeatsWeb do + pipe_through :browser + + get "/", RedirectController, :redirect_authenticated + get "/files/:id", FileController, :show + + delete "/signout", OAuthCallbackController, :sign_out + + live_session :default, on_mount: [{LiveBeatsWeb.UserAuth, :current_user}, LiveBeatsWeb.Nav] do + live "/signin", SignInLive, :index + end + + live_session :authenticated, + on_mount: [{LiveBeatsWeb.UserAuth, :ensure_authenticated}, LiveBeatsWeb.Nav] do + live "/songs/new", SongLive.Index, :new + live "/:user_id", SongLive.Index, :index + live "/profile/settings", SettingsLive, :edit + end + end end diff --git a/lib/live_beats_web/templates/layout/root.html.heex b/lib/live_beats_web/templates/layout/root.html.heex index 0cbc0d8..50f7490 100644 --- a/lib/live_beats_web/templates/layout/root.html.heex +++ b/lib/live_beats_web/templates/layout/root.html.heex @@ -38,18 +38,6 @@