mirror of
https://github.com/fly-apps/live_beats.git
synced 2024-11-21 23:50:59 +00:00
settings
This commit is contained in:
parent
4f474b6462
commit
ff7b064660
13 changed files with 201 additions and 68 deletions
|
@ -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"))
|
||||
|
|
|
@ -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
|
||||
|
|
11
lib/live_beats_web/controllers/redirect_controller.ex
Normal file
11
lib/live_beats_web/controllers/redirect_controller.ex
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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"""
|
||||
<svg class="inline-block animate-spin h-2.5 w-2.5 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
|
|
93
lib/live_beats_web/live/settings_live.ex
Normal file
93
lib/live_beats_web/live/settings_live.ex
Normal file
|
@ -0,0 +1,93 @@
|
|||
defmodule LiveBeatsWeb.SettingsLive do
|
||||
use LiveBeatsWeb, :live_view
|
||||
|
||||
alias LiveBeats.Accounts
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<.title_bar>
|
||||
Profile Settings
|
||||
</.title_bar>
|
||||
|
||||
<div class="max-w-3xl mx-auto mt-6">
|
||||
<.form let={f} for={@changeset} phx-change="validate" phx-submit="save" class="space-y-8 divide-y divide-gray-200">
|
||||
<div class="space-y-8 divide-y divide-gray-200">
|
||||
<div>
|
||||
<div>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
This information will be displayed publicly so be careful what you share.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
|
||||
<div class="sm:col-span-4">
|
||||
<label for="username" class="block text-sm font-medium text-gray-700">
|
||||
Username
|
||||
</label>
|
||||
<div class="mt-1 flex rounded-md shadow-sm">
|
||||
<span class="inline-flex items-center px-3 rounded-l-md border border-r-0 border-gray-300 bg-gray-50 text-gray-500 sm:text-sm">
|
||||
<%= URI.parse(LiveBeatsWeb.Endpoint.url()).host %>/
|
||||
</span>
|
||||
<%= 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" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sm:col-span-4">
|
||||
<label for="username" class="block text-sm font-medium text-gray-700">
|
||||
Email (from GitHub)
|
||||
</label>
|
||||
<div class="mt-1 flex rounded-md shadow-sm">
|
||||
<%= 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" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sm:col-span-4">
|
||||
<label for="about" class="block text-sm font-medium text-gray-700">
|
||||
Profile Tagline
|
||||
</label>
|
||||
<div class="mt-1">
|
||||
<%= 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" />
|
||||
</div>
|
||||
<p class="text-sm text-gray-500">Write a short tagline for your beats page.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-5">
|
||||
<div class="flex justify-end">
|
||||
<button type="submit" class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</.form>
|
||||
</div>
|
||||
"""
|
||||
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
|
|
@ -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
|
||||
|
|
|
@ -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"/>
|
||||
</div>
|
||||
<div class="col-span-full sm:grid sm:grid-cols-2 sm:gap-2 sm:items-start">
|
||||
<.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"/>
|
||||
</div>
|
||||
<div style={"width: #{@progress}%;"} class="col-span-full bg-purple-500 dark:bg-purple-400 h-1.5 w-0 p-0">
|
||||
</div>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -38,18 +38,6 @@
|
|||
<nav class="px-2">
|
||||
<div class="space-y-1">
|
||||
|
||||
<%= live_redirect to: Routes.home_path(@conn, :index),
|
||||
class: "bg-gray-100 text-gray-900 group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md" do %>
|
||||
<.icon name={:home} outlined class="text-gray-500 mr-3 flex-shrink-0 h-6 w-6"/>
|
||||
Home
|
||||
<% end %>
|
||||
|
||||
<a href="#"
|
||||
class="text-gray-600 hover:text-gray-900 hover:bg-gray-50 group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md">
|
||||
<.icon name={:music_note} outlined class="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6"/>
|
||||
My tasks
|
||||
</a>
|
||||
|
||||
<%= if @current_user do %>
|
||||
<!-- User account dropdown -->
|
||||
<div class="px-3 mt-6 relative inline-block text-left w-full">
|
||||
|
@ -207,16 +195,22 @@
|
|||
<nav class="px-3 mt-6">
|
||||
<div class="space-y-1">
|
||||
|
||||
<%= live_redirect to: Routes.home_path(@conn, :index),
|
||||
class: "bg-gray-200 text-gray-900 group flex items-center px-2 py-2 text-sm font-medium rounded-md" do %>
|
||||
<.icon name={:home} outlined class="text-gray-500 mr-3 flex-shrink-0 h-6 w-6"/>
|
||||
Home
|
||||
<% end %>
|
||||
<%= if @current_user do %>
|
||||
<.link
|
||||
redirect_to={Routes.song_index_path(@conn, :index, @current_user.username)}
|
||||
class="text-gray-700 hover:text-gray-900 hover:bg-gray-50 group flex items-center px-2 py-2 text-sm font-medium rounded-md"
|
||||
>
|
||||
<.icon name={:music_note} outlined class="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6"/>
|
||||
My Songs
|
||||
</.link>
|
||||
|
||||
<%= live_redirect to: Routes.song_index_path(@conn, :index),
|
||||
class: "text-gray-700 hover:text-gray-900 hover:bg-gray-50 group flex items-center px-2 py-2 text-sm font-medium rounded-md" do %>
|
||||
<.icon name={:music_note} outlined class="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6"/>
|
||||
My Songs
|
||||
<.link
|
||||
redirect_to={Routes.settings_path(@conn, :edit)}
|
||||
class="text-gray-700 hover:text-gray-900 hover:bg-gray-50 group flex items-center px-2 py-2 text-sm font-medium rounded-md"
|
||||
>
|
||||
<.icon name={:adjustments} outlined class="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6"/>
|
||||
Settings
|
||||
</.link>
|
||||
<% end %>
|
||||
|
||||
<%= unless @current_user do %>
|
||||
|
|
|
@ -22,16 +22,16 @@ defmodule LiveBeatsWeb.ErrorHelpers do
|
|||
|
||||
~H"""
|
||||
<%= for error <- @error_values do %>
|
||||
<div
|
||||
<span
|
||||
phx-feedback-for={@input_name}
|
||||
class={"invalid-feedback -mt-1 pl-2 text-sm text-white bg-red-600 rounded-md #{@class}"}
|
||||
class={"invalid-feedback inline-block pl-2 pr-2 text-sm text-white bg-red-600 rounded-md #{@class}"}
|
||||
>
|
||||
<%= translate_error(error) %>
|
||||
</div>
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
<%= if Enum.empty?(@error_values) do %>
|
||||
<div class={"invalid-feedback h-0 #{@class}"}></div>
|
||||
<span class={"invalid-feedback inline-block h-0 #{@class}"}></span>
|
||||
<% end %>
|
||||
"""
|
||||
end
|
||||
|
|
|
@ -10,6 +10,7 @@ defmodule LiveBeats.Repo.Migrations.CreateUserAuth do
|
|||
add :name, :string
|
||||
add :role, :string, null: false
|
||||
add :confirmed_at, :naive_datetime
|
||||
add :profile_tagline, :string
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
|
|
@ -86,7 +86,7 @@ defmodule LiveBeatsWeb.UserAuthTest do
|
|||
test "redirects if user is not authenticated", %{conn: conn} do
|
||||
conn = conn |> fetch_flash() |> UserAuth.require_authenticated_user([])
|
||||
assert conn.halted
|
||||
assert redirected_to(conn) == Routes.home_path(conn, :index)
|
||||
assert redirected_to(conn) == Routes.song_index_path(conn, :index)
|
||||
assert get_flash(conn, :error) == "You must log in to access this page."
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue