Janky initial implementation of auth and profiles

This commit is contained in:
Ryan Pandya 2022-10-29 17:21:52 -07:00
parent 2284a437fa
commit 90007d40e7
12 changed files with 232 additions and 76 deletions

View File

@ -2,6 +2,8 @@ defmodule Friends.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
@repo Friends.Repo
schema "users" do
field :email, :string
field :password, :string, virtual: true, redact: true
@ -13,6 +15,15 @@ defmodule Friends.Accounts.User do
has_one :profile, Friends.Friend
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 """
A user changeset for registration.

View File

@ -35,20 +35,26 @@ defmodule Friends.Friend do
def changeset(friend, params \\ %{}) do
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_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(:phone, Regex.compile!("^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$"))
|> Ecto.Changeset.validate_format(
: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(:email)
end
def all() do
preloads = [:relationships, :reverse_relationships]
preloads = [:relationships, :reverse_relationships, :user]
@repo.all(from(f in Friends.Friend, preload: ^preloads))
end
def new(params \\ %{}) do
%Friend{id: "new"}
|> struct(params)
@ -62,6 +68,7 @@ defmodule Friends.Friend do
)
)
end
def get_by_id(id) do
@repo.one(
from(f in Friend,
@ -71,49 +78,61 @@ defmodule Friends.Friend do
)
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
case params.id do
"new" ->
%Friend{} |> Friend.changeset(%{params | id: nil})
|> Map.put(:action, :insert)
|> @repo.insert!
_number ->
Friend.get_by_id(params.id |> String.to_integer)
|> Friend.changeset(params)
|> Map.put(:action, :update)
|> @repo.update!
%Friend{}
|> Friend.changeset(%{params | id: nil})
|> Map.put(:action, :insert)
|> @repo.insert!
_number ->
Friend.get_by_id(params.id |> String.to_integer())
|> Friend.changeset(params)
|> Map.put(:action, :update)
|> @repo.update!
end
end
def get_relationships(friend) do
friend
|> relations
|> Enum.map(&(relation(friend, &1)))
|> relations
|> Enum.map(&relation(friend, &1))
end
def get_events(friend) do
friend
|> get_relationships
|> Enum.map(&(&1.events))
|> List.flatten
|> get_relationships
|> Enum.map(& &1.events)
|> List.flatten()
end
def age(friend) do
# Age in years
Date.diff(Date.utc_today,friend.born) |> div(365)
Date.diff(Date.utc_today(), friend.born) |> div(365)
end
# TODO: Refactor
def create_birth_event(friend) do
if is_nil @repo.all(
from(e in Friends.Event,
where: e.name == "born"
)) |> List.flatten do
if is_nil(
@repo.all(
from(e in Friends.Event,
where: e.name == "born"
)
)
|> List.flatten()
) do
else
"find"
end
end
end

View File

@ -1,16 +1,63 @@
defmodule FriendsWeb.PageController do
use FriendsWeb, :controller
alias Friends.{Friend, Relationship}
import Helpers
alias Friends.{Friend, Repo, Accounts.User}
import Helpers.Names
plug :assign_profile
def index(conn, _params) do
new_friend = Friend.new |> Friend.changeset
new_friend = Friend.new() |> Friend.changeset()
conn
|> assign(:new_friend, new_friend)
|> assign(:all_friends, Friend.all)
|> assign(:users, Friends.Accounts.User |> Friends.Repo.all)
|> assign(:all_friends, Friend.all())
|> assign(:users, User |> Repo.all())
|> render("index.html")
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

View File

@ -91,7 +91,13 @@ defmodule FriendsWeb.UserAuth do
def fetch_current_user(conn, _opts) do
{user_token, conn} = ensure_user_token(conn)
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
defp ensure_user_token(conn) do

View File

@ -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

View File

@ -23,8 +23,8 @@ defmodule FriendsWeb.Router do
get "/", PageController, :index
get "/friends", FriendController, :index
live "/friend/:slug", FriendsLive.Show
live "/friend/:slug", FriendLive.Show
live "/friend/:slug/edit", FriendLive.Edit
end
# Other scopes may use custom stacks.
@ -65,6 +65,7 @@ defmodule FriendsWeb.Router do
scope "/", FriendsWeb do
pipe_through [:browser, :redirect_if_user_is_authenticated]
# Requires the user NOT be authenticated:
get "/users/register", UserRegistrationController, :new
post "/users/register", UserRegistrationController, :create
@ -78,6 +79,9 @@ defmodule FriendsWeb.Router do
scope "/", FriendsWeb do
pipe_through [:browser, :require_authenticated_user]
# Requires the user DO be authenticated:
live "/profile/new", ProfileLive.Form, :new
get "/users/settings", UserSettingsController, :edit
put "/users/settings", UserSettingsController, :update

View File

@ -3,27 +3,8 @@
<div class="flex-1">
<.link navigate={"/"} class="btn btn-ghost normal-case text-xl">Friends</.link>
</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">
<label tabindex="0" class="btn btn-ghost btn-circle avatar">
<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 class="dropdown dropdown-end">
<%= render("_user_menu.html", conn: @socket, current_user: @current_user) %>
</div>
</div>
</div>

View 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

View File

@ -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>

View 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

View File

@ -0,0 +1,3 @@
defmodule UserProfileView do
end

View File

@ -1,6 +1,4 @@
defmodule Helpers do
import Helpers.Names
def do!(thing) do
case thing do
{:ok, answer} -> answer
@ -8,21 +6,21 @@ defmodule Helpers do
end
end
def pluralize(qty, word) do
plural = if qty == 1 do
"#{word}"
else
"#{word}s"
end
plural =
if qty == 1 do
"#{word}"
else
"#{word}s"
end
"#{qty} #{plural}"
end
def parse_date(str), do: str |> to_string |> Timex.parse("%Y-%m-%d", :strftime)
def format_phone(str) do
str
|> String.replace(" ", "") |> String.replace("-", "") |> String.replace(".", "")
str |> String.replace(" ", "") |> String.replace("-", "") |> String.replace(".", "")
end
def to_slug(name) when is_binary(name) do
@ -30,6 +28,7 @@ defmodule Helpers do
|> String.replace(" ", "-")
|> String.downcase()
end
def to_slug(obj), do: to_slug(obj.name)
def from_slug(name) do
@ -41,17 +40,20 @@ defmodule Helpers do
end
def birthday(friend) do
this_year = (Date.utc_today
|> Calendar.strftime("%Y")
|> String.to_integer)
this_year =
Date.utc_today()
|> Calendar.strftime("%Y")
|> String.to_integer()
next_birthday = friend.born
|> Calendar.strftime("%m-%d")
next_birthday =
friend.born
|> Calendar.strftime("%m-%d")
next_birthdate = "#{this_year}-#{next_birthday}"
|> Date.from_iso8601!()
next_birthdate =
"#{this_year}-#{next_birthday}"
|> 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!()
else
next_birthdate
@ -59,7 +61,7 @@ defmodule Helpers do
end
def time_until_birthday(friend) do
birthday(friend) |> Date.diff(Date.utc_today)
birthday(friend) |> Date.diff(Date.utc_today())
end
def relations(friend) do
@ -74,5 +76,4 @@ defmodule Helpers do
def events(relationship) do
relationship.events
end
end