Janky initial implementation of auth and profiles
This commit is contained in:
parent
fe8fe1b9f7
commit
23e738ddc9
@ -2,6 +2,8 @@ defmodule Friends.Accounts.User do
|
|||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@repo Friends.Repo
|
||||||
|
|
||||||
schema "users" do
|
schema "users" do
|
||||||
field :email, :string
|
field :email, :string
|
||||||
field :password, :string, virtual: true, redact: true
|
field :password, :string, virtual: true, redact: true
|
||||||
@ -13,6 +15,15 @@ defmodule Friends.Accounts.User do
|
|||||||
has_one :profile, Friends.Friend
|
has_one :profile, Friends.Friend
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def load_profile(%Friends.Accounts.User{profile: %Ecto.Association.NotLoaded{}} = model) do
|
||||||
|
model
|
||||||
|
|> @repo.preload(:profile)
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_profile(nil) do
|
||||||
|
%{profile: nil}
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
A user changeset for registration.
|
A user changeset for registration.
|
||||||
|
|
||||||
|
|||||||
@ -35,20 +35,26 @@ defmodule Friends.Friend do
|
|||||||
|
|
||||||
def changeset(friend, params \\ %{}) do
|
def changeset(friend, params \\ %{}) do
|
||||||
friend
|
friend
|
||||||
|> Ecto.Changeset.cast(params, [:name, :born, :nickname, :email, :phone, :slug])
|
|> Ecto.Changeset.cast(params, [:name, :born, :nickname, :email, :phone, :slug, :user_id])
|
||||||
|> Ecto.Changeset.validate_required([:name, :email, :phone, :born])
|
|> Ecto.Changeset.validate_required([:name, :email, :phone, :born])
|
||||||
|> Ecto.Changeset.validate_format(:name, ~r/\w+\ \w+/)
|
|> Ecto.Changeset.validate_format(:name, ~r/\w+\ \w+/)
|
||||||
|> Ecto.Changeset.validate_format(:email, Regex.compile!("^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$"))
|
|> Ecto.Changeset.validate_format(
|
||||||
|> Ecto.Changeset.validate_format(:phone, Regex.compile!("^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$"))
|
:email,
|
||||||
|
Regex.compile!("^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$")
|
||||||
|
)
|
||||||
|
|> Ecto.Changeset.validate_format(
|
||||||
|
:phone,
|
||||||
|
Regex.compile!("^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$")
|
||||||
|
)
|
||||||
|> Ecto.Changeset.unique_constraint(:name)
|
|> Ecto.Changeset.unique_constraint(:name)
|
||||||
|
|> Ecto.Changeset.unique_constraint(:email)
|
||||||
end
|
end
|
||||||
|
|
||||||
def all() do
|
def all() do
|
||||||
preloads = [:relationships, :reverse_relationships]
|
preloads = [:relationships, :reverse_relationships, :user]
|
||||||
@repo.all(from(f in Friends.Friend, preload: ^preloads))
|
@repo.all(from(f in Friends.Friend, preload: ^preloads))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def new(params \\ %{}) do
|
def new(params \\ %{}) do
|
||||||
%Friend{id: "new"}
|
%Friend{id: "new"}
|
||||||
|> struct(params)
|
|> struct(params)
|
||||||
@ -62,6 +68,7 @@ defmodule Friends.Friend do
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_by_id(id) do
|
def get_by_id(id) do
|
||||||
@repo.one(
|
@repo.one(
|
||||||
from(f in Friend,
|
from(f in Friend,
|
||||||
@ -71,14 +78,25 @@ defmodule Friends.Friend do
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_by_email(email) do
|
||||||
|
@repo.one(
|
||||||
|
from(f in Friend,
|
||||||
|
where: f.email == ^email,
|
||||||
|
preload: [:relationships, :reverse_relationships]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def create_or_update(params) do
|
def create_or_update(params) do
|
||||||
case params.id do
|
case params.id do
|
||||||
"new" ->
|
"new" ->
|
||||||
%Friend{} |> Friend.changeset(%{params | id: nil})
|
%Friend{}
|
||||||
|
|> Friend.changeset(%{params | id: nil})
|
||||||
|> Map.put(:action, :insert)
|
|> Map.put(:action, :insert)
|
||||||
|> @repo.insert!
|
|> @repo.insert!
|
||||||
|
|
||||||
_number ->
|
_number ->
|
||||||
Friend.get_by_id(params.id |> String.to_integer)
|
Friend.get_by_id(params.id |> String.to_integer())
|
||||||
|> Friend.changeset(params)
|
|> Friend.changeset(params)
|
||||||
|> Map.put(:action, :update)
|
|> Map.put(:action, :update)
|
||||||
|> @repo.update!
|
|> @repo.update!
|
||||||
@ -88,32 +106,33 @@ defmodule Friends.Friend do
|
|||||||
def get_relationships(friend) do
|
def get_relationships(friend) do
|
||||||
friend
|
friend
|
||||||
|> relations
|
|> relations
|
||||||
|> Enum.map(&(relation(friend, &1)))
|
|> Enum.map(&relation(friend, &1))
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_events(friend) do
|
def get_events(friend) do
|
||||||
friend
|
friend
|
||||||
|> get_relationships
|
|> get_relationships
|
||||||
|> Enum.map(&(&1.events))
|
|> Enum.map(& &1.events)
|
||||||
|> List.flatten
|
|> List.flatten()
|
||||||
end
|
end
|
||||||
|
|
||||||
def age(friend) do
|
def age(friend) do
|
||||||
# Age in years
|
# Age in years
|
||||||
Date.diff(Date.utc_today,friend.born) |> div(365)
|
Date.diff(Date.utc_today(), friend.born) |> div(365)
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: Refactor
|
# TODO: Refactor
|
||||||
def create_birth_event(friend) do
|
def create_birth_event(friend) do
|
||||||
if is_nil @repo.all(
|
if is_nil(
|
||||||
|
@repo.all(
|
||||||
from(e in Friends.Event,
|
from(e in Friends.Event,
|
||||||
where: e.name == "born"
|
where: e.name == "born"
|
||||||
)) |> List.flatten do
|
)
|
||||||
|
)
|
||||||
|
|> List.flatten()
|
||||||
|
) do
|
||||||
else
|
else
|
||||||
"find"
|
"find"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,16 +1,63 @@
|
|||||||
defmodule FriendsWeb.PageController do
|
defmodule FriendsWeb.PageController do
|
||||||
use FriendsWeb, :controller
|
use FriendsWeb, :controller
|
||||||
alias Friends.{Friend, Relationship}
|
alias Friends.{Friend, Repo, Accounts.User}
|
||||||
import Helpers
|
|
||||||
|
import Helpers.Names
|
||||||
|
|
||||||
|
plug :assign_profile
|
||||||
|
|
||||||
def index(conn, _params) do
|
def index(conn, _params) do
|
||||||
|
new_friend = Friend.new() |> Friend.changeset()
|
||||||
new_friend = Friend.new |> Friend.changeset
|
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> assign(:new_friend, new_friend)
|
|> assign(:new_friend, new_friend)
|
||||||
|> assign(:all_friends, Friend.all)
|
|> assign(:all_friends, Friend.all())
|
||||||
|> assign(:users, Friends.Accounts.User |> Friends.Repo.all)
|
|> assign(:users, User |> Repo.all())
|
||||||
|> render("index.html")
|
|> render("index.html")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp assign_profile(conn, _opts) do
|
||||||
|
current_user = conn.assigns.current_user
|
||||||
|
|
||||||
|
case current_user do
|
||||||
|
nil ->
|
||||||
|
# No logged in user, and no profile
|
||||||
|
conn
|
||||||
|
|
||||||
|
user ->
|
||||||
|
case user.profile do
|
||||||
|
nil ->
|
||||||
|
# Logged in user, but no profile
|
||||||
|
# Is there a profile with the same email?
|
||||||
|
try_profile = user.email |> Friends.Friend.get_by_email()
|
||||||
|
|
||||||
|
case try_profile do
|
||||||
|
nil ->
|
||||||
|
# If not, we need to make a new profile
|
||||||
|
conn
|
||||||
|
|> redirect(to: Routes.profile_form_path(conn, :new))
|
||||||
|
|
||||||
|
# If so, link 'em and be done with it
|
||||||
|
friend ->
|
||||||
|
friend
|
||||||
|
|> Friends.Friend.changeset(%{
|
||||||
|
user_id: user.id
|
||||||
|
})
|
||||||
|
|> Friends.Repo.update!()
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(
|
||||||
|
:info,
|
||||||
|
"Welcome to the app, #{friend |> first_name()}!"
|
||||||
|
)
|
||||||
|
|> redirect(to: Routes.friend_path(conn, :index))
|
||||||
|
end
|
||||||
|
|
||||||
|
_profile ->
|
||||||
|
# Logged in user, and linked profile
|
||||||
|
conn
|
||||||
|
|> redirect(to: Routes.friend_path(conn, :index))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -91,7 +91,13 @@ defmodule FriendsWeb.UserAuth do
|
|||||||
def fetch_current_user(conn, _opts) do
|
def fetch_current_user(conn, _opts) do
|
||||||
{user_token, conn} = ensure_user_token(conn)
|
{user_token, conn} = ensure_user_token(conn)
|
||||||
user = user_token && Accounts.get_user_by_session_token(user_token)
|
user = user_token && Accounts.get_user_by_session_token(user_token)
|
||||||
assign(conn, :current_user, user)
|
|
||||||
|
assign(
|
||||||
|
conn,
|
||||||
|
:current_user,
|
||||||
|
user
|
||||||
|
|> Friends.Repo.preload(:profile)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp ensure_user_token(conn) do
|
defp ensure_user_token(conn) do
|
||||||
|
|||||||
@ -0,0 +1,32 @@
|
|||||||
|
defmodule FriendsWeb.UserProfileController do
|
||||||
|
use FriendsWeb, :controller
|
||||||
|
|
||||||
|
alias Friends.Accounts
|
||||||
|
alias FriendsWeb.Router.Helpers, as: Routes
|
||||||
|
|
||||||
|
plug :assign_profile
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Checks if the user has linked a profile yet,
|
||||||
|
and redirects to the profile creation flow
|
||||||
|
if not.
|
||||||
|
"""
|
||||||
|
def main(conn) do
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "test")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp assign_profile(conn, _opts) do
|
||||||
|
user = conn.assigns.current_user
|
||||||
|
|
||||||
|
msg = case user.profile do
|
||||||
|
nil -> "No profile!"
|
||||||
|
profile -> "Yes profile! #{profile.name}"
|
||||||
|
end
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, msg)
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -23,8 +23,8 @@ defmodule FriendsWeb.Router do
|
|||||||
get "/", PageController, :index
|
get "/", PageController, :index
|
||||||
get "/friends", FriendController, :index
|
get "/friends", FriendController, :index
|
||||||
|
|
||||||
live "/friend/:slug", FriendsLive.Show
|
live "/friend/:slug", FriendLive.Show
|
||||||
|
live "/friend/:slug/edit", FriendLive.Edit
|
||||||
end
|
end
|
||||||
|
|
||||||
# Other scopes may use custom stacks.
|
# Other scopes may use custom stacks.
|
||||||
@ -65,6 +65,7 @@ defmodule FriendsWeb.Router do
|
|||||||
|
|
||||||
scope "/", FriendsWeb do
|
scope "/", FriendsWeb do
|
||||||
pipe_through [:browser, :redirect_if_user_is_authenticated]
|
pipe_through [:browser, :redirect_if_user_is_authenticated]
|
||||||
|
# Requires the user NOT be authenticated:
|
||||||
|
|
||||||
get "/users/register", UserRegistrationController, :new
|
get "/users/register", UserRegistrationController, :new
|
||||||
post "/users/register", UserRegistrationController, :create
|
post "/users/register", UserRegistrationController, :create
|
||||||
@ -78,6 +79,9 @@ defmodule FriendsWeb.Router do
|
|||||||
|
|
||||||
scope "/", FriendsWeb do
|
scope "/", FriendsWeb do
|
||||||
pipe_through [:browser, :require_authenticated_user]
|
pipe_through [:browser, :require_authenticated_user]
|
||||||
|
# Requires the user DO be authenticated:
|
||||||
|
|
||||||
|
live "/profile/new", ProfileLive.Form, :new
|
||||||
|
|
||||||
get "/users/settings", UserSettingsController, :edit
|
get "/users/settings", UserSettingsController, :edit
|
||||||
put "/users/settings", UserSettingsController, :update
|
put "/users/settings", UserSettingsController, :update
|
||||||
|
|||||||
@ -3,27 +3,8 @@
|
|||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<.link navigate={"/"} class="btn btn-ghost normal-case text-xl">Friends</.link>
|
<.link navigate={"/"} class="btn btn-ghost normal-case text-xl">Friends</.link>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-none gap-2">
|
|
||||||
<div class="form-control hidden md:block">
|
|
||||||
<input type="text" placeholder="Search" class="input input-bordered" />
|
|
||||||
</div>
|
|
||||||
<div class="dropdown dropdown-end">
|
<div class="dropdown dropdown-end">
|
||||||
<label tabindex="0" class="btn btn-ghost btn-circle avatar">
|
<%= render("_user_menu.html", conn: @socket, current_user: @current_user) %>
|
||||||
<div class="w-10 rounded-full">
|
|
||||||
<img src="https://placeimg.com/80/80/people" />
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<ul tabindex="0" class="mt-3 p-2 shadow menu menu-compact dropdown-content bg-base-100 rounded-box w-52">
|
|
||||||
<li>
|
|
||||||
<a>
|
|
||||||
Profile
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="md:hidden"><a>Search</a></li>
|
|
||||||
<li><a>Settings</a></li>
|
|
||||||
<li><a>Logout</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
25
friends/lib/friends_web/templates/profile_live/form.ex
Normal file
25
friends/lib/friends_web/templates/profile_live/form.ex
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
defmodule FriendsWeb.ProfileLive.Form do
|
||||||
|
use FriendsWeb, :live_view
|
||||||
|
import FriendsWeb.LiveView
|
||||||
|
|
||||||
|
def mount(%{}, %{"user_token" => token}, socket) do
|
||||||
|
{:ok,
|
||||||
|
socket
|
||||||
|
|> assign_current_user(token)
|
||||||
|
|> title("Create a profile")}
|
||||||
|
end
|
||||||
|
|
||||||
|
def show_form(%{step: 1} = assigns) do
|
||||||
|
# First things first, are we linking to an existing user or no?
|
||||||
|
|
||||||
|
~H"""
|
||||||
|
<b>Your email:</b> <%= @current_user.email %>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
def show_form(%{step: 2} = assigns) do
|
||||||
|
~H"""
|
||||||
|
Step 2
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
<div class="prose">
|
||||||
|
<h1>Welcome!</h1>
|
||||||
|
<p>You have made a user account, but we don't yet have a profile for you.</p>
|
||||||
|
|
||||||
|
<div id="profile-form">
|
||||||
|
<.show_form step={1} current_user={@current_user} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
18
friends/lib/friends_web/views/live_view.ex
Normal file
18
friends/lib/friends_web/views/live_view.ex
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
defmodule FriendsWeb.LiveView do
|
||||||
|
use FriendsWeb, :live_component
|
||||||
|
|
||||||
|
def assign_current_user(socket, user_token) do
|
||||||
|
socket
|
||||||
|
|> assign(
|
||||||
|
:current_user,
|
||||||
|
user_token
|
||||||
|
|> Friends.Accounts.get_user_by_session_token()
|
||||||
|
|> Friends.Repo.preload(:profile)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def title(socket, title) do
|
||||||
|
socket
|
||||||
|
|> assign(:page_title, title)
|
||||||
|
end
|
||||||
|
end
|
||||||
3
friends/lib/friends_web/views/user_profile_view.ex
Normal file
3
friends/lib/friends_web/views/user_profile_view.ex
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
defmodule UserProfileView do
|
||||||
|
|
||||||
|
end
|
||||||
@ -1,6 +1,4 @@
|
|||||||
defmodule Helpers do
|
defmodule Helpers do
|
||||||
import Helpers.Names
|
|
||||||
|
|
||||||
def do!(thing) do
|
def do!(thing) do
|
||||||
case thing do
|
case thing do
|
||||||
{:ok, answer} -> answer
|
{:ok, answer} -> answer
|
||||||
@ -8,21 +6,21 @@ defmodule Helpers do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def pluralize(qty, word) do
|
def pluralize(qty, word) do
|
||||||
plural = if qty == 1 do
|
plural =
|
||||||
|
if qty == 1 do
|
||||||
"#{word}"
|
"#{word}"
|
||||||
else
|
else
|
||||||
"#{word}s"
|
"#{word}s"
|
||||||
end
|
end
|
||||||
|
|
||||||
"#{qty} #{plural}"
|
"#{qty} #{plural}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_date(str), do: str |> to_string |> Timex.parse("%Y-%m-%d", :strftime)
|
def parse_date(str), do: str |> to_string |> Timex.parse("%Y-%m-%d", :strftime)
|
||||||
|
|
||||||
def format_phone(str) do
|
def format_phone(str) do
|
||||||
str
|
str |> String.replace(" ", "") |> String.replace("-", "") |> String.replace(".", "")
|
||||||
|> String.replace(" ", "") |> String.replace("-", "") |> String.replace(".", "")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_slug(name) when is_binary(name) do
|
def to_slug(name) when is_binary(name) do
|
||||||
@ -30,6 +28,7 @@ defmodule Helpers do
|
|||||||
|> String.replace(" ", "-")
|
|> String.replace(" ", "-")
|
||||||
|> String.downcase()
|
|> String.downcase()
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_slug(obj), do: to_slug(obj.name)
|
def to_slug(obj), do: to_slug(obj.name)
|
||||||
|
|
||||||
def from_slug(name) do
|
def from_slug(name) do
|
||||||
@ -41,17 +40,20 @@ defmodule Helpers do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def birthday(friend) do
|
def birthday(friend) do
|
||||||
this_year = (Date.utc_today
|
this_year =
|
||||||
|
Date.utc_today()
|
||||||
|> Calendar.strftime("%Y")
|
|> Calendar.strftime("%Y")
|
||||||
|> String.to_integer)
|
|> String.to_integer()
|
||||||
|
|
||||||
next_birthday = friend.born
|
next_birthday =
|
||||||
|
friend.born
|
||||||
|> Calendar.strftime("%m-%d")
|
|> Calendar.strftime("%m-%d")
|
||||||
|
|
||||||
next_birthdate = "#{this_year}-#{next_birthday}"
|
next_birthdate =
|
||||||
|
"#{this_year}-#{next_birthday}"
|
||||||
|> Date.from_iso8601!()
|
|> Date.from_iso8601!()
|
||||||
|
|
||||||
if next_birthdate |> Date.diff(Date.utc_today) < 0 do
|
if next_birthdate |> Date.diff(Date.utc_today()) < 0 do
|
||||||
"#{this_year + 1}-#{next_birthday}" |> Date.from_iso8601!()
|
"#{this_year + 1}-#{next_birthday}" |> Date.from_iso8601!()
|
||||||
else
|
else
|
||||||
next_birthdate
|
next_birthdate
|
||||||
@ -59,7 +61,7 @@ defmodule Helpers do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def time_until_birthday(friend) do
|
def time_until_birthday(friend) do
|
||||||
birthday(friend) |> Date.diff(Date.utc_today)
|
birthday(friend) |> Date.diff(Date.utc_today())
|
||||||
end
|
end
|
||||||
|
|
||||||
def relations(friend) do
|
def relations(friend) do
|
||||||
@ -74,5 +76,4 @@ defmodule Helpers do
|
|||||||
def events(relationship) do
|
def events(relationship) do
|
||||||
relationship.events
|
relationship.events
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user