Compare commits

..

No commits in common. "5ca34c2b6f6d400100182359eccb5f95a41d0289" and "90007d40e79c1106d4500003ece75377ad0ecf07" have entirely different histories.

51 changed files with 477 additions and 1450 deletions

View File

@ -4,11 +4,9 @@ import Config
config :friends, Friends.Repo, config :friends, Friends.Repo,
username: "postgres", username: "postgres",
password: "pleasework", password: "pleasework",
# hostname: "10.0.0.22", hostname: "10.0.0.22",
hostname: "localhost",
database: "friends_crm", database: "friends_crm",
#port: "2345", port: "2345",
port: "5432",
stacktrace: true, stacktrace: true,
show_sensitive_data_on_connection_error: true, show_sensitive_data_on_connection_error: true,
pool_size: 10 pool_size: 10

View File

@ -12,10 +12,9 @@ config :bcrypt_elixir, :log_rounds, 1
config :friends, Friends.Repo, config :friends, Friends.Repo,
username: "postgres", username: "postgres",
password: "pleasework", password: "pleasework",
#hostname: "10.0.0.22", hostname: "10.0.0.22",
#port: "2345", port: "2345",
hostname: "localhost", port: "5432", database: "friends_test#{System.get_env("MIX_TEST_PARTITION")}",
database: "friends_test",
pool: Ecto.Adapters.SQL.Sandbox, pool: Ecto.Adapters.SQL.Sandbox,
pool_size: 10 pool_size: 10

View File

@ -76,43 +76,10 @@ defmodule Friends.Accounts do
""" """
def register_user(attrs) do def register_user(attrs) do
%User{} %User{}
|> Repo.preload(:profile)
|> User.registration_changeset(attrs) |> User.registration_changeset(attrs)
|> Repo.insert() |> Repo.insert()
end end
@doc """
Gets or links a user profile if it exists.
## Examples
iex> assign_profile(%User{email: some_friend@exists.com})
{:ok, friend}
iex> assign_profile(%User{email: another_friend@exists.com})
iex> assign_profile
"""
def assign_profile(%User{} = user) do
case user.email |> Friends.Friend.get_by_email do
nil ->
nil
friend ->
new_friend = friend
|> Friends.Friend.changeset(%{
user_id: user.id})
|> Friends.Repo.update!()
|> Friends.Friend.load_user
%{
friend: new_friend,
user: new_friend.user |> Friends.Accounts.User.load_profile
}
end
end
@doc """ @doc """
Returns an `%Ecto.Changeset{}` for tracking user changes. Returns an `%Ecto.Changeset{}` for tracking user changes.

View File

@ -1,56 +0,0 @@
defmodule Friends.Event do
use Ecto.Schema
alias Friends.{Relationship,Event}
alias Places.Place
@repo Friends.Repo
schema "events" do
field(:date, :date)
field(:name, :string)
field(:story, :string)
field(:defining, :boolean)
field(:solo, :boolean)
belongs_to(:place, Place)
belongs_to(:relationship, Relationship)
end
def meet(friend1, friend2, opts \\ nil) do
relationship = Relationship.get_or_new(friend1, friend2)
opts = opts ++ [if opts[:place] do
{
:place_id,
Places.Place.get_or_new(opts[:place]).id
}
end]
{:ok, event} = %Event{
story: opts[:story],
date: opts[:date],
place_id: opts[:place_id],
relationship_id: relationship.id
} |> @repo.insert
event
end
def people(event) do
event.relationship |> Relationship.members
end
def age(event) do
years = Date.diff(Date.utc_today, event.date)
|> div(365)
if years == 0 do
months = Date.diff(Date.utc_today, event.date)
|> rem(365) |> div(12)
{months, :month}
else
{years, :year}
end
end
def print_age(age) do
Friends.Helpers.pluralize(age |> elem(0), age |> elem(1))
end
end

View File

@ -17,7 +17,6 @@ defmodule Friends.Friend do
field(:memories, {:array, :string}) field(:memories, {:array, :string})
belongs_to :user, Friends.Accounts.User, foreign_key: :user_id belongs_to :user, Friends.Accounts.User, foreign_key: :user_id
belongs_to :address, Friends.Places.Place, foreign_key: :address_id
many_to_many( many_to_many(
:relationships, :relationships,
@ -52,12 +51,12 @@ defmodule Friends.Friend do
end end
def all() do def all() do
preloads = [:relationships, :reverse_relationships, :user, :address] 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)
end end
@ -65,7 +64,7 @@ defmodule Friends.Friend do
@repo.one( @repo.one(
from(f in Friend, from(f in Friend,
where: f.slug == ^slug, where: f.slug == ^slug,
preload: [:relationships, :reverse_relationships, :address] preload: [:relationships, :reverse_relationships]
) )
) )
end end
@ -74,7 +73,7 @@ defmodule Friends.Friend do
@repo.one( @repo.one(
from(f in Friend, from(f in Friend,
where: f.id == ^id, where: f.id == ^id,
preload: [:relationships, :reverse_relationships, :address] preload: [:relationships, :reverse_relationships]
) )
) )
end end
@ -88,52 +87,19 @@ defmodule Friends.Friend do
) )
end end
def create(params) do
%Friend{}
|> Friend.changeset(%{params | id: nil})
|> Map.put(:action, :insert)
end
def update(params) do
Friend.get_by_id(params.id |> String.to_integer())
|> Friend.changeset(params)
|> Map.put(:action, :update)
end
def commit(changeset) do
changeset
|> generate_slug
|> @repo.insert!
|> load_preloads
end
def generate_slug(%Ecto.Changeset{} = changeset) do
slug = changeset.changes.name |> to_slug
changeset
|> changeset(%{
slug: slug
})
end
def generate_slug(%Friend{} = friend) do
%{
friend
| slug: friend.name |> to_slug
}
end
def create_or_update(params) do def create_or_update(params) do
case params.id do case params.id do
:new -> "new" ->
params %Friend{}
|> create() |> Friend.changeset(%{params | id: nil})
|> commit() |> Map.put(:action, :insert)
|> @repo.insert!
_number -> _number ->
params Friend.get_by_id(params.id |> String.to_integer())
|> update() |> Friend.changeset(params)
|> commit() |> Map.put(:action, :update)
|> @repo.update!
end end
end end
@ -155,50 +121,18 @@ defmodule Friends.Friend do
Date.diff(Date.utc_today(), friend.born) |> div(365) Date.diff(Date.utc_today(), friend.born) |> div(365)
end end
def can_be_edited_by(friend, user) do # TODO: Refactor
if user |> is_nil(), do: false, else: friend.id == user.profile.id def create_birth_event(friend) do
end if is_nil(
@repo.all(
def assign_user(%Friend{} = friend) do from(e in Friends.Event,
case friend.email |> Friends.Accounts.get_user_by_email() do where: e.name == "born"
nil -> )
nil )
|> List.flatten()
user -> ) do
user |> Friends.Accounts.assign_profile() else
"find"
end end
end end
def load_user(%Friends.Friend{user: %Ecto.Association.NotLoaded{}} = model) do
model
|> @repo.preload(:user)
end
def load_user(%Friends.Friend{} = friend), do: friend
def load_relationships(
%Friends.Friend{
relationships: %Ecto.Association.NotLoaded{},
reverse_relationships: %Ecto.Association.NotLoaded{}
} = model
) do
model
|> @repo.preload([:relationships, :reverse_relationships])
end
def load_relationships(%Friends.Friend{} = friend), do: friend
def load_preloads(
%Friends.Friend{
relationships: %Ecto.Association.NotLoaded{},
reverse_relationships: %Ecto.Association.NotLoaded{},
user: %Ecto.Association.NotLoaded{},
address: %Ecto.Association.NotLoaded{}
} = model
) do
model
|> @repo.preload([:user, :relationships, :reverse_relationships, :address])
end
def load_preloads(%Friends.Friend{} = friend), do: friend
end end

View File

@ -1,61 +0,0 @@
defmodule Friends.Places.Place do
use Ecto.Schema
import Ecto.Query
import Helpers
@repo Friends.Repo
schema "places" do
field(:name, :string)
field(:type, :string)
field(:latlon, {:array, :float})
field(:zoom, :integer)
has_many(:events, Friends.Event)
has_many(:friends, Friends.Friend)
end
def new(place_name, opts \\ nil) do
{:ok, place} =
@repo.insert(%Friends.Places.Place{
name: place_name,
type: opts[:type]
})
place
end
def get(place_name) do
@repo.one(
from(p in Place,
where: p.name == ^place_name,
preload: [:events]
)
)
end
def get_or_new(name, opts \\ nil) do
case get(name) do
nil -> new(name, opts)
place -> place
end
end
def get_by_slug(slug) do
name = slug |> from_slug
@repo.one(
from(p in Place,
where: p.name == ^name,
preload: [:events]
)
)
end
def changeset(place, params \\ %{}) do
place
|> Ecto.Changeset.cast(params, [:name, :type, :latlon, :zoom])
|> Ecto.Changeset.validate_required([:name])
|> Ecto.Changeset.unique_constraint(:name)
end
end

View File

@ -1,27 +0,0 @@
defmodule Friends.Places.Search do
def api_key, do: "pk.7c4a6f4bf061fd4a9af9663132c58af3"
def viewbox(region) do
[lat_min, lat_max, lon_min, lon_max] = region |> Enum.map(&String.to_float/1)
[
lat_min - 1,
lat_max + 1,
lon_min - 1,
lon_max + 1
]
end
def query(str, region \\ nil) do
viewbox =
if region do
viewbox(query(region))
end
url = "https://us1.locationiq.com/v1/search?key=#{api_key()}&q=#{str}&format=json#{viewbox}"
response = HTTPoison.get!(url)
results = Poison.decode!(response.body)
results
end
end

View File

@ -1,7 +1,7 @@
defmodule Friends.Relationship do defmodule Friends.Relationship do
use Ecto.Schema use Ecto.Schema
import Ecto.Query import Ecto.Query
alias Friends.{Relationship,Friend} alias Friends.{Relationship,Event,Friend}
@repo Friends.Repo @repo Friends.Repo
@ -74,7 +74,7 @@ defmodule Friends.Relationship do
@repo.all(from(r in Friends.Relationship, preload: ^preloads)) @repo.all(from(r in Friends.Relationship, preload: ^preloads))
end end
def new(friend1, friend2, type \\ 2) do def new(friend1, friend2, type \\ 0) do
id1 = friend1.id id1 = friend1.id
id2 = friend2.id id2 = friend2.id
{:ok, relationship} = @repo.insert( {:ok, relationship} = @repo.insert(
@ -136,17 +136,4 @@ defmodule Friends.Relationship do
end) |> Enum.sort |> List.last |> div(365) end) |> Enum.sort |> List.last |> div(365)
end end
def load_events(%Relationship{
events: %Ecto.Association.NotLoaded{}} = model) do
model
|> @repo.preload([:events])
end
def load_events(%Relationship{} = r), do: r
def load_preloads(%Relationship{
events: %Ecto.Association.NotLoaded{}} = model) do
model
|> @repo.preload([:events])
end
def load_preloads(%Relationship{} = r), do: r
end end

View File

@ -1,12 +1,13 @@
defmodule FriendsWeb.FriendsController do defmodule FriendsWeb.FriendController do
use FriendsWeb, :controller use FriendsWeb, :controller
alias Friends.{Friend, Relationship} alias Friends.{Friend, Relationship}
alias Friends.Accounts.User alias Friends.Accounts.User
import Helpers import Helpers
def index(conn, _params) do def index(conn, _params) do
conn conn
|> assign(:all_friends, Friend.all()) |> assign(:all_friends, Friend.all)
|> render("index.html") |> render("index.html")
end end
end end

View File

@ -1,21 +1,63 @@
defmodule FriendsWeb.PageController do defmodule FriendsWeb.PageController do
use FriendsWeb, :controller use FriendsWeb, :controller
alias Friends.{Friend, Repo, Accounts.User, Accounts} alias Friends.{Friend, Repo, Accounts.User}
import Helpers.Names 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()
if(conn |> get_session(:linked)) do conn
# If logged in and linked, redirect to friend_path :index |> assign(:new_friend, new_friend)
conn |> redirect(to: FriendsWeb.Router.Helpers.friends_path(conn, :index)) |> assign(:all_friends, Friend.all())
else |> assign(:users, User |> Repo.all())
# Otherwise, show the landing page |> render("index.html")
conn end
|> assign(:new_friend, new_friend)
|> assign(:friends, Friend.all()) defp assign_profile(conn, _opts) do
|> render("index.html") 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
end end

View File

@ -127,58 +127,13 @@ defmodule FriendsWeb.UserAuth do
end end
end end
@doc """
Used to detect or link a profile to a user.
"""
def capture_profile(conn, _opts) do
current_user = conn.assigns[:current_user]
if current_user do
# There's a user, but is there a profile?
profile = current_user |> Map.get(:profile)
case profile do
nil ->
# No profile linked
conn
|> create_or_link_profile(current_user)
_profile ->
# Profile already linked
conn |> put_session(:linked, true)
end
else
# Not logged in
conn
end
end
defp create_or_link_profile(conn, user) do
case user.email |> Friends.Friend.get_by_email() do
# Find a profile and link it
nil ->
conn
|> put_flash(:info, "You're logged in but we still need to make you a profile!")
|> redirect(to: Routes.friends_edit_path(conn, :overview, :new))
|> halt()
# Or make a new one
_profile ->
user
|> Friends.Accounts.assign_profile()
conn
|> put_session(:linked, true)
end
end
@doc """ @doc """
Used for routes that require the user to be authenticated. Used for routes that require the user to be authenticated.
If you want to enforce the user email is confirmed before If you want to enforce the user email is confirmed before
they use the application at all, here would be a good place. they use the application at all, here would be a good place.
""" """
def require_authenticated_user(conn, _opts \\ nil) do def require_authenticated_user(conn, _opts) do
if conn.assigns[:current_user] do if conn.assigns[:current_user] do
conn conn
else else

View File

@ -10,8 +10,7 @@ defmodule FriendsWeb.Endpoint do
signing_salt: "jNBoklme" signing_salt: "jNBoklme"
] ]
socket "/live", Phoenix.LiveView.Socket, socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]
websocket: [connect_info: [:peer_data, session: @session_options]]
# Serve at "/" the static files from "priv/static" directory. # Serve at "/" the static files from "priv/static" directory.
# #

View File

@ -1,212 +0,0 @@
defmodule FriendsWeb.FriendsLive.Components do
use FriendsWeb, :live_component
use Phoenix.HTML
import Helpers
alias Friends.Friend
def header(assigns) do
~H"""
<div class="border-b-4 flex flex-row justify-between">
<h1 class="mb-0 pl-2" style="height:48px; text-indent:5px"><%= @friend.name %></h1>
</div>
"""
end
def menu(assigns) do
~H"""
<div class="hidden sm:tabs sm:mb-8">
<%= for page <- [:overview, :timeline, :relationships] do %>
<% is_active = if(page == @live_action) do "tab-active" end %>
<.link patch={Routes.friends_show_path(FriendsWeb.Endpoint, page, @friend.slug)} class={"font-bold sm:tab-lg flex-grow no-underline tab tab-lifted #{is_active}"}>
<%= page |> to_string |> :string.titlecase() %>
</.link>
<% end %>
</div>
"""
end
def edit_menu(assigns) do
~H"""
<div class="hidden sm:tabs sm:mb-8">
<%= for page <- [:overview, :timeline, :relationships] do %>
<% is_active = if(page == @live_action) do "tab-active" end %>
<.link patch={Routes.friends_edit_path(FriendsWeb.Endpoint, page, @friend.slug)} class={"font-bold sm:tab-lg flex-grow no-underline tab tab-lifted #{is_active}"}>
<%= page |> to_string |> :string.titlecase() %>
</.link>
<% end %>
</div>
"""
end
def show_page(:main, assigns), do: show_page(:overview, %{assigns | live_action: :overview})
def show_page(:overview, assigns) do
~H"""
<ul class="py-4 pl-0 md:text-xl h-1/2">
<li class="flex flex-row mb-8 gap-6">
<strong class="w-28 text-right">Nickname:</strong>
<div class="">
<%= if is_nil(@friend.nickname) do %>
<span class="italic">none</span>
<% else %>
<%= @friend.nickname %>
<% end %>
</div>
</li>
<li class="flex flex-row mb-8 gap-6">
<strong class="w-28 text-right">Birthday:</strong>
<div class=""><%= @friend.born |> Calendar.strftime("%B %d, %Y") %>
<br class="md:hidden"/>
<span class="font-light">(<%= @friend |> Friend.age %> years old)</span>
</div>
</li>
<li class="flex flex-row mb-8 gap-6">
<strong class="w-28 text-right">Email:</strong>
<div class=""><%= @friend.email %></div>
</li>
<li class="flex flex-row mb-8 gap-6">
<strong class="w-28 text-right">Phone:</strong>
<div class=""><%= @friend.phone %></div>
</li>
<li class="flex flex-row mb-8 gap-6">
<strong class="w-28 text-right">Address:</strong>
<div class=""><%= @friend.address %></div>
</li>
</ul>
"""
end
def show_page(:timeline, assigns) do
~H"""
<div id="timeline" class="flex md:flex-row flex-col gap-8 p-8">
<%= for event <- @friend |> Friends.Friend.get_events do %>
<ul>
<li>
<%= event.name %>
</li>
</ul>
<% end %>
<%= if @friend |> Friends.Friend.get_events |> Enum.empty? do %>
<div class="italic">None yet.</div>
<% end %>
</div>
"""
end
def show_page(:relationships, assigns) do
~H"""
<div id="relationships" class="flex md:flex-row flex-col gap-8">
<%= for relation <- @friend |> relations do %>
<% relationship = relation(@friend, relation) %>
<div id={"relation-#{relation.id}"} class="card card-compact w-96 bg-base-100 shadow-xl">
<figure><img src="https://placeimg.com/400/225/people" alt={relation.id} /></figure>
<div class="card-body">
<h3 class="card-title">
<%= relation.name %>
<%= if relationship |> Friends.Relationship.get_relation do %>
<div class={"badge badge-#{relationship |> Friends.Relationship.get_color}"}><%= relationship |> Friends.Relationship.get_relation %></div>
<% end %>
</h3>
<p>If a dog chews shoes whose shoes does he choose?</p>
</div>
</div>
<% end %>
<%= if @friend |> relations |> Enum.empty? do %>
<div class="italic p-4">No relationships on record yet.</div>
<% end %>
</div>
"""
end
###
def edit_page(:overview, assigns) do
~H"""
<%= @peer_data.address |> Tuple.to_list |> Enum.join(".") %>
<.form
for={@changeset}
let={f}
action={@action}
phx_change= "validate"
phx_submit= "save">
<%= hidden_input f, :id, value: @friend.id %>
<div class="border-b-4 flex flex-row">
<%= text_input f, :name, placeholder: "Full Name",
class: "m-0 p-0 pb-2 pl-2 input input-bordered border-dashed",
style: "color: var(--tw-prose-headings);
font-weight: 800;
font-size: 2.25em;
min-width: 50%;
text-indent: 4px;
line-height: 1.1111111;",
value: @friend.name,
phx_debounce: :blur %>
<div class="min-w-fit flex place-items-center mx-4"><%= error_tag f, :name %></div>
</div>
<ul class="py-4 pl-0 h-1/2">
<li class="flex flex-row gap-x-6 h-16">
<strong class="md:text-xl w-20 md:w-28 shrink-0 text-right">Nickname:</strong>
<div class=""><%= text_input f, :nickname, class: "input input-primary input-sm md:input-md", phx_debounce: "blur", value: @friend.nickname %></div>
</li>
<li class="flex flex-row gap-x-6">
<strong class="md:text-xl w-20 md:w-28 shrink-0 text-right">Birthday:</strong>
<div class="flex flex-col h-16">
<%= date_input f, :born, class: "input input-primary input-sm md:input-md", phx_debounce: "blur", value: @friend.born %>
<div class="min-w-fit flex place-items-center mr-4"><%= error_tag f, :born %></div>
</div>
</li>
<li class="flex flex-row gap-x-6">
<strong class="md:text-xl w-20 md:w-28 shrink-0 text-right">Email:</strong>
<div class="flex flex-col h-16">
<%= text_input f, :email, class: "input input-primary input-sm md:input-md", phx_debounce: "blur", value: @friend.email %>
<div class="min-w-fit flex place-items-center mr-4"><%= error_tag f, :email %></div>
</div>
</li>
<li class="flex flex-row gap-x-6">
<strong class="md:text-xl w-20 md:w-28 shrink-0 text-right">Phone:</strong>
<div class="flex flex-col h-16">
<%= text_input f, :phone, class: "input input-primary input-sm md:input-md", phx_debounce: "blur", value: @friend.phone %>
<div class="min-w-fit flex place-items-center mr-4"><%= error_tag f, :phone %></div>
</div>
</li>
<li class="flex flex-row gap-x-6">
<strong class="md:text-xl w-20 md:w-28 shrink-0 text-right">Address:</strong>
<div class="flex flex-col h-16">
<%= text_input f, :address_query, class: "input input-primary input-sm md:input-md", phx_throttle: "500", value: @address_query %>
<%= hidden_input f, :address_id, value: 0 %>
</div>
</li>
</ul>
<div class="form-control flex flex-row gap-x-4 md:justify-end mb-4 md:w-1/2">
<div class="flex-1">
<.link patch={Routes.friends_show_path(FriendsWeb.Endpoint, :overview, @friend.slug)} class="btn btn-block btn-outline">back</.link>
</div>
<div class="flex-1">
<%= if @changeset.valid? do %>
<%= submit "Save", phx_disable_with: "Saving...", class: "btn btn-block" %>
<% else %>
<%= submit "Save", class: "btn btn-block btn-disabled" %>
<% end %>
</div>
<%= if @live_action != :new do %>
<div class="flex-1">
<button phx-click="delete" phx-value-friend_id={@friend.id} class="btn btn-block btn-error">Delete</button>
</div>
<% end %>
</div>
</.form>
"""
end
def edit_page(:relationships, assigns) do
~H"""
"""
end
def edit_page(:timeline, assigns) do
~H"""
"""
end
end

View File

@ -1,126 +0,0 @@
defmodule FriendsWeb.FriendsLive.Edit do
use FriendsWeb, :live_view
import FriendsWeb.LiveHelpers
import FriendsWeb.FriendsLive.Components
import Helpers
import Helpers.Names
alias Friends.{Friend, Places}
def mount(%{"slug" => slug} = _attrs, token, socket) do
live_action = socket.assigns.live_action || false
friend = Friend.get_by_slug(slug)
editable = friend |> Friend.can_be_edited_by(socket.assigns[:current_user])
# address_viewbox = Places.Search.query()
if(live_action) do
{:ok,
socket
|> assign(:live_action, live_action)
|> assign_current_user(token |> Map.get("user_token"))
|> assign(:friend, friend)
|> title(friend.name <> " - " <> (live_action |> titlecase))
|> assign(:changeset, %Friend{} |> Friend.changeset())
|> assign(:action, editable)
|> assign(:address_query, nil)
|> assign(:peer_data, get_connect_info(socket, :peer_data))}
else
{:ok, socket |> redirect(to: Routes.friends_show_path(socket, :overview, friend.slug))}
end
end
def handle_params(%{"slug" => slug} = attrs, _url, socket) do
live_action = socket.assigns.live_action || false
friend = Friend.get_by_slug(slug)
editable = friend |> Friend.can_be_edited_by(socket.assigns[:current_user])
{:noreply,
socket
|> assign_friend(friend)
|> assign(:live_action, live_action)
|> title(friend.name <> " - " <> (live_action |> titlecase))
|> assign(:editable, editable)}
end
def handle_event("validate", %{"friend" => form_params}, %{assigns: %{friend: friend}} = socket) do
id = form_params["id"]
name = form_params["name"]
nickname = form_params["nickname"]
born = form_params["born"]
email = form_params["email"]
phone = form_params["phone"] |> format_phone
address_query = form_params["address_query"]
new_params = %{
id: id,
name: name,
nickname: nickname,
slug: friend.slug,
born: born,
phone: phone,
email: email
}
changeset =
%Friend{}
|> Friend.changeset(new_params)
|> Map.put(:action, :validate)
{
:noreply,
socket
|> assign(:changeset, changeset)
|> assign_friend(friend |> struct(new_params), changeset)
|> assign(:address_query, address_query)
}
end
# Handle form saving
def handle_event(
"save",
%{"friend" => form_params},
%{assigns: %{changeset: changeset}} = socket
) do
name = form_params["name"]
nickname = form_params["nickname"]
born = form_params["born"]
email = form_params["email"]
phone = form_params["phone"] |> format_phone
id = form_params["id"]
new_params = %{
id: id,
name: name,
nickname: nickname,
slug: name |> to_slug,
born: born,
phone: phone,
email: email
}
updated_friend = Friend.create_or_update(new_params)
new_changeset = updated_friend |> Friend.changeset()
{
:noreply,
socket
|> put_flash(:info, "Saved #{updated_friend |> first_name}!")
|> assign(:new_friend, new_changeset)
|> assign(:friend, updated_friend)
|> push_patch(to: "/friend/#{updated_friend.slug}")
}
end
# Handle deleting a friend
def handle_event("delete", %{"friend_id" => friend_id}, socket) do
friend = Friend.get_by_id(friend_id)
{:noreply,
socket
|> put_flash(:error, "Deleted '#{friend.name}'.")
|> push_navigate(to: "/")}
end
end

View File

@ -1,6 +0,0 @@
<section class="row">
<article class="column prose">
<%= edit_menu(assigns) %>
<%= edit_page(@live_action, assigns) %>
</article>
</section>

View File

@ -1,31 +1,189 @@
defmodule FriendsWeb.FriendsLive.Friend do defmodule FriendsWeb.FriendLive.Friend do
use FriendsWeb, :live_view use FriendsWeb, :live_view
use Phoenix.HTML
alias FriendsWeb.FriendsLive.Components
alias FriendsWeb.Router.Helpers, as: Routes
alias Friends.Friend
import FriendsWeb.LiveHelpers
import Helpers import Helpers
import Helpers.Names import Helpers.Names
alias Friends.{Friend,Relationship}
alias FriendsWeb.FriendLive.Components
alias FriendsWeb.Router.Helpers, as: Routes
# Initialize variables on first load # Initialize variables on first load
def mount(%{}, token, socket) do def mount(%{}, _token, socket) do
{:ok, {:ok, socket
socket |> title("New Friend")
|> title("New Friend") |> assign(:changeset, %Friend{} |> Friend.changeset)
|> assign_current_user(token |> Map.get("user_token")) }
|> assign(:changeset, %Friend{} |> Friend.changeset())} end
# Show Friend
def handle_params(%{"slug" => slug} = attrs, _token, socket) do
friend = Friend.get_by_slug(slug)
page = if (attrs |> Map.get("page")) in ["overview", "calendar", "relationships"] do
attrs["page"]
else
"overview"
end
{:noreply, socket
|> title(friend.name <> " - " <> (page |> :string.titlecase()))
|> assign_friend(friend)
|> page_view(page)
|> assign(:action, Routes.friend_path(socket, :update, friend.id))
}
end end
# New Friend # New Friend
def handle_params(_attrs, _token, socket) do def handle_params(attrs, _token, socket) do
friend = Friend.new() friend = Friend.new
{:noreply, socket
|> title("New Friend")
|> assign_friend(friend)
|> page_view("overview")
|> assign(:action,Routes.friend_path(socket, :create))
}
end
# Handle form validation
def handle_event("validate", %{"friend" => form_params}, %{assigns: %{friend: friend}} = socket) do
id = form_params["id"]
name = form_params["name"]
nickname = form_params["nickname"]
born = form_params["born"]
email = form_params["email"]
phone = form_params["phone"] |> format_phone
new_params = %{
id: id,
name: name,
nickname: nickname,
slug: friend.slug,
born: born,
phone: phone,
email: email
}
changeset = %Friend{}
|> Friend.changeset(new_params)
|> Map.put(:action, :validate)
{
:noreply, socket
|> assign(:changeset, changeset)
|> assign_friend(friend |> struct(new_params), changeset)
}
end
# Handle form saving
def handle_event("save", %{"friend" => form_params},%{assigns: %{changeset: changeset}} = socket) do
name = form_params["name"]
nickname = form_params["nickname"]
born = form_params["born"]
email = form_params["email"]
phone = form_params["phone"] |> format_phone
id = form_params["id"]
new_params = %{
id: id,
name: name,
nickname: nickname,
slug: name |> to_slug,
born: born,
phone: phone,
email: email
}
updated_friend = Friend.create_or_update(new_params)
new_changeset = updated_friend |> Friend.changeset
{
:noreply,
socket
|> put_flash(:info, "Saved #{updated_friend |> first_name}!")
|> assign(:new_friend, new_changeset)
|> assign(:friend, updated_friend)
|> push_patch(to: "/friend/#{updated_friend.slug}")
}
end
# Handle deleting a friend
def handle_event("delete", %{"friend_id" => friend_id}, socket) do
friend = Friend.get_by_id(friend_id)
{:noreply, {:noreply,
socket socket
|> title("New Friend") |> put_flash(:error, "Deleted '#{friend.name}'.")
|> assign_friend(friend) |> push_navigate(to: "/")
|> assign(:action, Routes.friends_path(socket, :create))} }
end
# Set page title
def title(socket, title) do
socket |> assign(:page_title, title)
end end
# Set variables on page: friend, changeset, relationships
def assign_friend(socket, friend) do
socket
|> assign(:friend, friend)
|> assign(:changeset, friend |> Friend.changeset)
|> assign(:relationships, friend.relationships)
end
# Same thing, but this time we have a changeset we want to keep
def assign_friend(socket, friend, changeset) do
socket
|> assign(:friend, friend)
|> assign(:changeset, changeset)
|> assign(:relationships, friend.relationships)
end
# Set page_view variable
def page_view(socket, page) do
socket |> assign(:page_view, page)
end
# Route to the right sub-template in Components/Components.ex
def content(assigns) do
~H"""
<%= if @live_action != :new do %>
<%= FriendsWeb.FriendLive.Friend.menu(assigns) %>
<% end %>
<%= cond do %>
<% @live_action == :show -> %>
<%= header(assigns) %>
<%= @page_view |> Components.show_page(assigns) %>
<% @live_action in [:edit, :new] -> %>
<%= @page_view |> Components.form_page(assigns) %>
<% end %>
"""
end
def header(assigns) do
~H"""
<div class="border-b-4 flex flex-row justify-between">
<h1 class="mb-0 pl-2" style="height:48px; text-indent:5px"><%= @friend.name %></h1>
</div>
"""
end
def menu(assigns) do
~H"""
<div class="hidden sm:tabs sm:mb-8">
<%= for page <- ["overview", "calendar", "relationships"] do %>
<% is_active = if(page == @page_view) do "tab-active" end %>
<.link patch={"/friend/#{@friend.slug}?page=#{page}"} class={"font-bold sm:tab-lg flex-grow no-underline tab tab-lifted #{is_active}"}>
<%= page |> :string.titlecase() %>
</.link>
<% end %>
</div>
"""
end
end end

View File

@ -1,5 +1,5 @@
<section class="row"> <section class="row">
<article class="column prose"> <article class="column prose">
<FriendsWeb.FriendsLive.Friend.content friend={@friend} page_view={@page_view} changeset={@changeset} action={@action} live_action={@live_action}/> <FriendsWeb.FriendLive.Friend.content friend={@friend} page_view={@page_view} changeset={@changeset} action={@action} live_action={@live_action}/>
</article> </article>
</section> </section>

View File

@ -1,3 +1,4 @@
defmodule FriendsWeb.FriendsLive do defmodule FriendsWeb.FriendsLive do
use FriendsWeb, :live_view use FriendsWeb, :live_view
end end

View File

@ -1,40 +0,0 @@
defmodule FriendsWeb.FriendsLive.Show do
use FriendsWeb, :live_view
import FriendsWeb.LiveHelpers
import FriendsWeb.FriendsLive.Components
alias Friends.Friend
def mount(%{"slug" => slug} = _attrs, token, socket) do
live_action = socket.assigns.live_action || false
friend = Friend.get_by_slug(slug)
editable = friend |> Friend.can_be_edited_by(socket.assigns[:current_user])
if(live_action) do
{:ok,
socket
|> assign(:live_action, live_action)
|> assign_current_user(token |> Map.get("user_token"))
|> assign(:friend, friend)
|> title(friend.name <> " - " <> (live_action |> titlecase))
|> assign(:changeset, %Friend{} |> Friend.changeset())
|> assign(:action, editable)}
else
{:ok, socket |> redirect(to: Routes.friends_show_path(socket, :overview, friend.slug))}
end
end
def handle_params(%{"slug" => slug} = attrs, _url, socket) do
live_action = socket.assigns.live_action || false
friend = Friend.get_by_slug(slug)
editable = friend |> Friend.can_be_edited_by(socket.assigns[:current_user])
{:noreply,
socket
|> assign_friend(friend)
|> assign(:live_action, live_action)
|> title(friend.name <> " - " <> (live_action |> titlecase))
|> assign(:editable, editable)}
end
end

View File

@ -1,13 +0,0 @@
<section class="row">
<article class="column prose">
<%= menu(assigns) %>
<%= header(assigns) %>
<%= show_page(@live_action, assigns) %>
<%= if @editable do %>
<div class="form-control flex flex-row mb-4">
<.link navigate={Routes.friends_edit_path(FriendsWeb.Endpoint, :overview, @friend.slug)} class="btn btn-block md:btn-wide text-white">edit</.link>
</div>
<% end %>
</article>
</section>

View File

@ -17,6 +17,16 @@ defmodule FriendsWeb.Router do
plug :accepts, ["json"] plug :accepts, ["json"]
end end
scope "/", FriendsWeb do
pipe_through :browser
get "/", PageController, :index
get "/friends", FriendController, :index
live "/friend/:slug", FriendLive.Show
live "/friend/:slug/edit", FriendLive.Edit
end
# Other scopes may use custom stacks. # Other scopes may use custom stacks.
# scope "/api", FriendsWeb do # scope "/api", FriendsWeb do
# pipe_through :api # pipe_through :api
@ -52,65 +62,39 @@ defmodule FriendsWeb.Router do
end end
## Authentication routes ## Authentication routes
# Routes that only work if user not authenticated
scope "/users", FriendsWeb do
pipe_through [:browser, :redirect_if_user_is_authenticated]
get "/register", UserRegistrationController, :new
post "/register", UserRegistrationController, :create
get "/log_in", UserSessionController, :new
post "/log_in", UserSessionController, :create
get "/reset_password", UserResetPasswordController, :new
post "/reset_password", UserResetPasswordController, :create
get "/reset_password/:token", UserResetPasswordController, :edit
put "/reset_password/:token", UserResetPasswordController, :update
end
# Confirmation and logout
scope "/users", FriendsWeb do
pipe_through [:browser]
delete "/log_out", UserSessionController, :delete
get "/confirm", UserConfirmationController, :new
post "/confirm", UserConfirmationController, :create
get "/confirm/:token", UserConfirmationController, :edit
post "/confirm/:token", UserConfirmationController, :update
end
# Routes that require the user be authenticated:
scope "/users/settings", FriendsWeb do
pipe_through [:browser, :require_authenticated_user]
get "/", UserSettingsController, :edit
put "/", UserSettingsController, :update
get "/confirm_email/:token", UserSettingsController, :confirm_email
end
# THE ACTUAL GUTS OF THE APP
scope "/", FriendsWeb do scope "/", FriendsWeb do
pipe_through [:browser, :capture_profile] pipe_through [:browser, :redirect_if_user_is_authenticated]
get "/", PageController, :index # Requires the user NOT be authenticated:
get "/users/register", UserRegistrationController, :new
post "/users/register", UserRegistrationController, :create
get "/users/log_in", UserSessionController, :new
post "/users/log_in", UserSessionController, :create
get "/users/reset_password", UserResetPasswordController, :new
post "/users/reset_password", UserResetPasswordController, :create
get "/users/reset_password/:token", UserResetPasswordController, :edit
put "/users/reset_password/:token", UserResetPasswordController, :update
end end
# View-only modes (don't require being logged in and having a profile) scope "/", FriendsWeb do
scope "/friends", 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
get "/users/settings/confirm_email/:token", UserSettingsController, :confirm_email
end
scope "/", FriendsWeb do
pipe_through [:browser] pipe_through [:browser]
get "/", FriendsController, :index
end
scope "/friend", FriendsWeb do delete "/users/log_out", UserSessionController, :delete
pipe_through [:browser] get "/users/confirm", UserConfirmationController, :new
live "/:slug", FriendsLive.Show post "/users/confirm", UserConfirmationController, :create
live "/:slug/overview", FriendsLive.Show, :overview get "/users/confirm/:token", UserConfirmationController, :edit
live "/:slug/timeline", FriendsLive.Show, :timeline post "/users/confirm/:token", UserConfirmationController, :update
live "/:slug/relationships", FriendsLive.Show, :relationships
end
# Edit modes (require being logged in and having a profile)
scope "/friend/update/", FriendsWeb do
pipe_through [:browser, :require_authenticated_user, :capture_profile]
live "/:slug/overview", FriendsLive.Edit, :overview
live "/:slug/timeline", FriendsLive.Edit, :timeline
live "/:slug/relationships", FriendsLive.Edit, :relationships
post "/:slug/update", FriendsController, :update
end end
end end

View File

@ -0,0 +1,13 @@
<h1>All Friends</h1>
<ul class="text-xl">
<%= for f <- @all_friends do %>
<li>
<.link href={"/friend/#{f.slug}"}><%= f.name %></.link>
<%= if f.id == @current_user.profile.id do %>
(you)
<% end %>
</li>
<% end %>
</ul>

View File

@ -1,19 +0,0 @@
<h1>All Friends</h1>
<ul class="text-xl">
<%= for f <- @all_friends do %>
<li>
<.link href={Routes.friends_show_path(@conn, :overview, f.slug)}><%= f.name %></.link>
<%= if @current_user do %>
<%= if f.id == @current_user.profile.id do %>
(you)
<% end %><% end %>
</li>
<% end %>
</ul>
<%= if @current_user do %>
<div class="m-4 mt-16">
<.link href={Routes.friends_edit_path(@conn, :overview, :new)}>Add Friend</.link>
</div>
<% end %>

View File

@ -1,12 +1,16 @@
<ul class="p-2 shadow menu menu-compact dropdown-content bg-base-100 text-neutral rounded-box w-52 flex flex-col gap-4">
<%= if @current_user do %> <%= if @current_user do %>
<li class="p-2 pb-4 border-b-2"><%= @current_user.email %></li> <label tabindex="0" class="btn btn-ghost btn-circle avatar">
<li><%= link "Settings", to: Routes.user_settings_path(@conn, :edit) %></li> <div class="w-10 rounded-full">
<li><%= link "Log out", to: Routes.user_session_path(@conn, :delete), method: :delete %></li> <img src="https://placeimg.com/80/80/people" />
</div>
</label>
<ul class="mt-3 p-2 shadow menu menu-compact dropdown-content bg-base-100 text-neutral rounded-box w-52">
<li><%= @current_user.email %></li>
<li><%= link "Settings", to: Routes.user_settings_path(@conn, :edit) %></li>
<li><%= link "Log out", to: Routes.user_session_path(@conn, :delete), method: :delete %></li>
</ul>
<% else %> <% else %>
<%= link "Log in", to: Routes.user_session_path(@conn, :new), class: "btn" %> <%= link "Log in", to: Routes.user_session_path(@conn, :new), class: "btn" %>
<%= link "Register", to: Routes.user_registration_path(@conn, :new), class: "btn btn-primary" %> <%= link "Register", to: Routes.user_registration_path(@conn, :new), class: "btn btn-primary" %>
<%= link "Log in", to: Routes.user_session_path(@conn, :new), class: "btn" %>
<%= link "Register", to: Routes.user_registration_path(@conn, :new), class: "btn btn-primary" %>
<% end %> <% end %>

View File

@ -9,7 +9,7 @@
and basically go back to the 2011 Facebook we all miss. and basically go back to the 2011 Facebook we all miss.
</p> </p>
<div class="hidden md:block"> <div class="hidden md:block">
<.friends_list friends={@friends} /> <.db_stats all_friends={@all_friends} users={@users} />
</div> </div>
</div> </div>
<div id="sign-up" class="prose card md:w-fit bg-neutral-content text-neutral shadow-xl items-center"> <div id="sign-up" class="prose card md:w-fit bg-neutral-content text-neutral shadow-xl items-center">
@ -20,7 +20,7 @@
<.sign_up new_friend={@new_friend} /> <.sign_up new_friend={@new_friend} />
</div> </div>
</div> </div>
<div id="friends" class="prose w-full mb-8 md:hidden"> <div id="stats" class="prose w-full mb-8 md:hidden">
<.friends_list friends={@friends} /> <.db_stats all_friends={@all_friends} users={@users} />
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
defmodule FriendsWeb.ProfileLive.Form do defmodule FriendsWeb.ProfileLive.Form do
use FriendsWeb, :live_view use FriendsWeb, :live_view
import FriendsWeb.LiveHelpers import FriendsWeb.LiveView
def mount(%{}, %{"user_token" => token}, socket) do def mount(%{}, %{"user_token" => token}, socket) do
{:ok, {:ok,

View File

@ -3,26 +3,22 @@
<.form let={f} for={@changeset} action={Routes.user_registration_path(@conn, :create)}> <.form let={f} for={@changeset} action={Routes.user_registration_path(@conn, :create)}>
<%= if @changeset.action do %> <%= if @changeset.action do %>
<div class="alert alert-danger"> <div class="alert alert-danger">
Oops, something went wrong! Please check the errors below. <p>Oops, something went wrong! Please check the errors below.</p>
</div> </div>
<% end %> <% end %>
<ul class="w-1/2 pl-0 flex flex-col gap-6">
<li class="flex flex-row gap-4">
<%= label f, :email, class: "w-1/3" %>
<%= email_input f, :email, required: true %>
<%= error_tag f, :email %>
</li>
<li class="flex flex-row gap-4">
<%= label f, :password, class: "w-1/3" %>
<%= password_input f, :password, required: true %>
<%= error_tag f, :password %>
</li>
<li class="flex">
<%= submit "Register", class: "btn btn-primary" %>
</li>
</ul>
</.form>
<%= label f, :email %>
<%= email_input f, :email, required: true %>
<%= error_tag f, :email %>
<%= label f, :password %>
<%= password_input f, :password, required: true %>
<%= error_tag f, :password %>
<div>
<%= submit "Register" %>
</div>
</.form>
<p> <p>
<%= link "Log in", to: Routes.user_session_path(@conn, :new) %> | <%= link "Log in", to: Routes.user_session_path(@conn, :new) %> |

View File

@ -6,23 +6,19 @@
<p><%= @error_message %></p> <p><%= @error_message %></p>
</div> </div>
<% end %> <% end %>
<ul class="w-1/2 pl-0 flex flex-col gap-6">
<li class="flex flex-row gap-4"> <%= label f, :email %>
<%= label f, :email, class: "w-1/3" %> <%= email_input f, :email, required: true %>
<%= email_input f, :email, required: true %>
</li> <%= label f, :password %>
<li class="flex flex-row gap-4"> <%= password_input f, :password, required: true %>
<%= label f, :password, class: "w-1/3" %>
<%= password_input f, :password, required: true %> <%= label f, :remember_me, "Keep me logged in for 60 days" %>
</li> <%= checkbox f, :remember_me %>
<li class="flex flex-row gap-4">
<%= label f, :remember_me, "Keep me logged in for 60 days", class: "w-fit" %> <div>
<%= checkbox f, :remember_me %> <%= submit "Log in" %>
</li> </div>
<li class="flex">
<%= submit "Log in", class: "btn btn-primary" %>
</li>
</ul>
</.form> </.form>
<p> <p>

View File

@ -1,78 +1,53 @@
<h1>Settings</h1> <h1>Settings</h1>
<div class="flex gap-16 flex-col md:flex-row justify-evenly"> <h3>Change email</h3>
<.form
let={f}
for={@email_changeset}
action={Routes.user_settings_path(@conn, :update)}
id="update_email"
class="w-max"
>
<%= if @email_changeset.action do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<%= hidden_input f, :action, name: "action", value: "update_email" %> <.form let={f} for={@email_changeset} action={Routes.user_settings_path(@conn, :update)} id="update_email">
<%= if @email_changeset.action do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<%= hidden_input f, :action, name: "action", value: "update_email" %>
<h3>Change email</h3> <%= label f, :email %>
<ul class="w-full pl-0 flex flex-col gap-6"> <%= email_input f, :email, required: true %>
<%= error_tag f, :email %>
<li class="flex flex-row gap-4"> <%= label f, :current_password, for: "current_password_for_email" %>
<%= label f, :email, class: "w-1/3" %> <%= password_input f, :current_password, required: true, name: "current_password", id: "current_password_for_email" %>
<%= email_input f, :email, required: true %> <%= error_tag f, :current_password %>
<%= error_tag f, :email %>
</li>
<li class="flex flex-row gap-4">
<%= label f, :current_password, for: "current_password_for_email", class: "w-1/3" %>
<%= password_input f, :password, required: true %>
<%= error_tag f, :current_password %>
</li>
<li class="flex place-self-stretch">
<%= submit "Change email", class: "btn btn-primary" %>
</li>
</ul>
</.form>
<.form <div>
let={f} <%= submit "Change email" %>
for={@password_changeset} </div>
action={Routes.user_settings_path(@conn, :update)} </.form>
id="update_password"
class="w-max"
>
<h3>Change password</h3> <h3>Change password</h3>
<ul class="w-full pl-0 flex flex-col gap-6 md:h-full"> <.form let={f} for={@password_changeset} action={Routes.user_settings_path(@conn, :update)} id="update_password">
<%= if @password_changeset.action do %> <%= if @password_changeset.action do %>
<div class="alert alert-danger"> <div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p> <p>Oops, something went wrong! Please check the errors below.</p>
</div> </div>
<% end %> <% end %>
<%= hidden_input f, :action, name: "action", value: "update_password" %> <%= hidden_input f, :action, name: "action", value: "update_password" %>
<li class="flex flex-row gap-4"> <%= label f, :password, "New password" %>
<%= label f, :current_password, for: "current_password_for_password", class: "w-1/3" %> <%= password_input f, :password, required: true %>
<%= password_input f, :current_password, required: true, name: "current_password", id: "current_password_for_password" %> <%= error_tag f, :password %>
<%= error_tag f, :current_password %>
</li> <%= label f, :password_confirmation, "Confirm new password" %>
<li class="flex flex-row gap-4"> <%= password_input f, :password_confirmation, required: true %>
<%= label f, :password, "New password", class: "w-1/3" %> <%= error_tag f, :password_confirmation %>
<%= password_input f, :password, required: true, class: "shrink-0" %>
<%= error_tag f, :password %> <%= label f, :current_password, for: "current_password_for_password" %>
</li> <%= password_input f, :current_password, required: true, name: "current_password", id: "current_password_for_password" %>
<li class="flex flex-row gap-4"> <%= error_tag f, :current_password %>
<%= label f, :password_confirmation, "Confirm new password", class: "w-1/3" %>
<%= password_input f, :password_confirmation, required: true %> <div>
<%= error_tag f, :password_confirmation %> <%= submit "Change password" %>
</li> </div>
<li class="flex"> </.form>
<%= submit "Change password", class: "btn btn-primary" %>
</li>
</ul>
</.form>
</div>

View File

@ -1,5 +1,6 @@
defmodule FriendsWeb.FriendsView do defmodule FriendsWeb.FriendView do
use FriendsWeb, :view use FriendsWeb, :view
import Phoenix.Component import Phoenix.Component
import Helpers import Helpers
end end

View File

@ -1,49 +0,0 @@
defmodule FriendsWeb.LiveHelpers do
use FriendsWeb, :live_component
alias Friends.Friend
def titlecase(atom) do
atom |> to_string |> :string.titlecase()
end
def assign_current_user(socket, user_token) do
user =
case user_token do
nil ->
nil
_moot ->
user_token
|> Friends.Accounts.get_user_by_session_token()
|> Friends.Repo.preload(:profile)
end
socket
|> assign(
:current_user,
user
)
end
# Set page title variable
def title(socket, title) do
socket
|> assign(:page_title, title)
end
# Set variables on page: friend, changeset, relationships
def assign_friend(socket, friend) do
socket
|> assign(:friend, friend)
|> assign(:changeset, friend |> Friend.changeset())
|> assign(:relationships, friend.relationships)
end
# Same thing, but this time we have a changeset we want to keep
def assign_friend(socket, friend, changeset) do
socket
|> assign(:friend, friend)
|> assign(:changeset, changeset)
|> assign(:relationships, friend.relationships)
end
end

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

@ -6,25 +6,24 @@ defmodule FriendsWeb.PageView do
alias FriendsWeb.Components.SignUp, as: SignUp alias FriendsWeb.Components.SignUp, as: SignUp
def sign_up(assigns), do: SignUp.sign_up_form(assigns) def sign_up(assigns), do: SignUp.sign_up_form(assigns)
def db_stats(assigns) do
def friends_list(assigns) do
~H""" ~H"""
<h3 class="mt-12 border-b-2"> <h3 class="mt-12 border-b-2">
All Friends Database stats
</h3> </h3>
<ul> <ul>
<%= for friend <- @friends do %> <li>
<li> <.link patch="/friends" class="">
<div id={"friend-#{friend.id}"} class=""> <%= pluralize(@all_friends |> length, "friend") %>
<div class=""> </.link>
<h3 class=""> </li>
<.link href={Routes.live_path(FriendsWeb.Endpoint, FriendsWeb.FriendsLive.Show, friend.slug)}><%= friend.name %></.link> <li>
</h3> <.link patch="/users" class="">
</div> <%= pluralize(@users |> length, "user") %>
</div> </.link>
</li> </li>
<% end %> </ul>
</ul> """
"""
end end
end end

View File

@ -19,20 +19,10 @@ defmodule Helpers do
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 valid_phone() do
Regex.compile!("^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$")
end
def format_phone(str) do def format_phone(str) do
str str |> String.replace(" ", "") |> String.replace("-", "") |> String.replace(".", "")
|> String.replace(" ", "")
|> String.replace("-", "")
|> String.replace(".", "")
|> String.replace(~r/[\(\)]/, "")
end end
def to_slug(nil), do: nil
def to_slug(name) when is_binary(name) do def to_slug(name) when is_binary(name) do
name name
|> String.replace(" ", "-") |> String.replace(" ", "-")

View File

@ -54,10 +54,7 @@ defmodule Friends.MixProject do
{:tailwind, "~> 0.1.6", runtime: Mix.env() == :dev}, {:tailwind, "~> 0.1.6", runtime: Mix.env() == :dev},
{:earmark, "~> 1.4"}, {:earmark, "~> 1.4"},
{:html_sanitize_ex, "~> 1.3"}, {:html_sanitize_ex, "~> 1.3"},
{:yamerl, github: "yakaz/yamerl"}, {:yamerl, github: "yakaz/yamerl"}
{:iamvery, "~> 0.6"},
{:httpoison, "~> 1.8"},
{:poison, "~> 5.0"}
] ]
end end

View File

@ -22,8 +22,6 @@
"hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.4.2", "c479398b6de798c03eb5d04a0a9a9159d73508f83f6590a00b8eacba3619cf4c", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm", "aef6c28585d06a9109ad591507e508854c5559561f950bbaea773900dd369b0e"}, "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.4.2", "c479398b6de798c03eb5d04a0a9a9159d73508f83f6590a00b8eacba3619cf4c", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm", "aef6c28585d06a9109ad591507e508854c5559561f950bbaea773900dd369b0e"},
"httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
"iamvery": {:hex, :iamvery, "0.6.0", "6df5a753023cb4ea281f96f1c311d9af39e5e0d8328e2db5fa9923036ea3ddc0", [:mix], [], "hexpm", "6c408c7b1e4dc1c8736470f88a40177559b2dd898f27cf250574e87585f9a925"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
@ -42,7 +40,6 @@
"plug": {:hex, :plug, "1.13.6", "187beb6b67c6cec50503e940f0434ea4692b19384d47e5fdfd701e93cadb4cc2", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02b9c6b9955bce92c829f31d6284bf53c591ca63c4fb9ff81dfd0418667a34ff"}, "plug": {:hex, :plug, "1.13.6", "187beb6b67c6cec50503e940f0434ea4692b19384d47e5fdfd701e93cadb4cc2", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02b9c6b9955bce92c829f31d6284bf53c591ca63c4fb9ff81dfd0418667a34ff"},
"plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"}, "plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"},
"plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"}, "plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"},
"poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"},
"postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"}, "postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},

View File

@ -1,9 +0,0 @@
defmodule Friends.Repo.Migrations.AddAddressesToFriends do
use Ecto.Migration
def change do
alter table(:friends) do
add :address_id, references(:places)
end
end
end

View File

@ -3,7 +3,7 @@ defmodule Friends.AccountsTest do
alias Friends.Accounts alias Friends.Accounts
import Friends.{AccountsFixtures, FriendsFixtures} import Friends.AccountsFixtures
alias Friends.Accounts.{User, UserToken} alias Friends.Accounts.{User, UserToken}
describe "get_user_by_email/1" do describe "get_user_by_email/1" do
@ -62,8 +62,9 @@ defmodule Friends.AccountsTest do
{:error, changeset} = Accounts.register_user(%{email: "not valid", password: "not valid"}) {:error, changeset} = Accounts.register_user(%{email: "not valid", password: "not valid"})
assert %{ assert %{
email: ["must have the @ sign and no spaces"] email: ["must have the @ sign and no spaces"],
} = errors_on(changeset) password: ["should be at least 12 character(s)"]
} = errors_on(changeset)
end end
test "validates maximum values for email and password for security" do test "validates maximum values for email and password for security" do
@ -266,6 +267,7 @@ defmodule Friends.AccountsTest do
}) })
assert %{ assert %{
password: ["should be at least 12 character(s)"],
password_confirmation: ["does not match password"] password_confirmation: ["does not match password"]
} = errors_on(changeset) } = errors_on(changeset)
end end
@ -474,6 +476,7 @@ defmodule Friends.AccountsTest do
}) })
assert %{ assert %{
password: ["should be at least 12 character(s)"],
password_confirmation: ["does not match password"] password_confirmation: ["does not match password"]
} = errors_on(changeset) } = errors_on(changeset)
end end
@ -502,29 +505,4 @@ defmodule Friends.AccountsTest do
refute inspect(%User{password: "123456"}) =~ "password: \"123456\"" refute inspect(%User{password: "123456"}) =~ "password: \"123456\""
end end
end end
describe "assign_profile/1" do
setup do
user = user_fixture()
%{
user: user,
friend: friend_fixture(%{email: user.email})
}
end
test "links a friend to a user", %{user: user, friend: friend} do
assert user.profile == nil
assert friend.user == nil
%{
friend: new_friend,
user: new_user
} = user |> Friends.Accounts.assign_profile
assert new_user.profile.id == friend.id
assert new_friend.user.id == user.id
end
end
end end

View File

@ -1,126 +0,0 @@
defmodule Friends.FriendsTest do
use Friends.DataCase
alias Friends.Friend
import Friends.{AccountsFixtures, FriendsFixtures}
# alias Friends.Accounts.{User, UserToken}
describe "new/1" do
test "no params" do
f = Friend.new()
assert f.id == :new
end
test "with params" do
attrs = valid_friend_attributes()
f = attrs |> Friend.new()
assert f.id == :new
assert f.name == attrs |> Map.get(:name)
end
test "has no loaded preloads" do
f = Friend.new()
assert %Ecto.Association.NotLoaded{} = f.user
assert %Ecto.Association.NotLoaded{} = f.relationships
assert %Ecto.Association.NotLoaded{} = f.reverse_relationships
end
end
describe "commit/1" do
test "changes id" do
f =
valid_friend_attributes()
|> Friend.create()
|> Friend.commit()
assert f.id != :new
end
test "generates slug" do
f =
valid_friend_attributes()
|> Friend.create()
|> Friend.commit()
assert f.slug != nil
assert f.slug == f.name |> Helpers.to_slug()
end
end
describe "generate_slug/1" do
test "a new friend has no slug" do
f = valid_friend_attributes() |> Friend.new()
assert f.slug == nil
end
test "generate_slug generates a slug, returns a friend" do
f = valid_friend_attributes() |> Friend.new() |> Friend.generate_slug()
assert f.slug == f.name |> Helpers.to_slug()
end
test "generate_slug generates a slug, returns a changeset" do
c = valid_friend_attributes() |> Friend.create() |> Friend.generate_slug()
f = c.data
assert f.slug == f.name |> Helpers.to_slug()
end
end
describe "assign_user/1" do
setup do
friend = friend_fixture()
%{
friend: friend,
user: user_fixture(%{email: friend.email})
}
end
test "links a user to a friend", %{user: user, friend: friend} do
assert user.profile == nil
assert friend.user == nil
%{
friend: new_friend,
user: new_user
} = friend |> Friends.Friend.assign_user()
assert new_user.profile.id == friend.id
assert new_friend.user.id == user.id
end
end
describe "preloads" do
setup do
%{friend: Friend.new(%{id: 123})}
end
test "default nothing loaded", %{friend: friend} do
f = friend
refute f.user |> Ecto.assoc_loaded?()
refute f.relationships |> Ecto.assoc_loaded?()
refute f.reverse_relationships |> Ecto.assoc_loaded?()
end
test "load user", %{friend: friend} do
f = friend |> Friend.load_user()
assert f.user |> Ecto.assoc_loaded?()
refute f.relationships |> Ecto.assoc_loaded?()
refute f.reverse_relationships |> Ecto.assoc_loaded?()
end
test "load relationships", %{friend: friend} do
f = friend |> Friend.load_relationships()
refute f.user |> Ecto.assoc_loaded?()
assert f.relationships |> Ecto.assoc_loaded?()
assert f.reverse_relationships |> Ecto.assoc_loaded?()
end
test "load all", %{friend: friend} do
f = friend |> Friend.load_preloads()
assert f.user |> Ecto.assoc_loaded?()
assert f.relationships |> Ecto.assoc_loaded?()
assert f.reverse_relationships |> Ecto.assoc_loaded?()
end
end
end

View File

@ -1,39 +0,0 @@
defmodule Friends.HelpersTest do
use Friends.DataCase
#alias Friends.Accounts
#import Friends.AccountsFixtures
#alias Friends.Accounts.{User, UserToken}
import Helpers
describe "format_phone/1" do
test "regular US phone number, spaces" do
str = "+1 203 848 8633"
formatted_str = format_phone(str)
assert formatted_str == "+12038488633"
end
test "regular US phone number, no spaces" do
str = "+12038488633"
formatted_str = format_phone(str)
assert formatted_str == "+12038488633"
end
test "regular US phone number, dashes" do
str = "+1-203-848-8633"
formatted_str = format_phone(str)
assert formatted_str == "+12038488633"
end
test "regular US phone number, usual formatting" do
str = "+1 (203) 848-8633"
formatted_str = format_phone(str)
assert formatted_str == "+12038488633"
end
test "regular US phone number, no country code" do
str = "(203) 848-8633"
formatted_str = format_phone(str)
assert formatted_str == "2038488633"
end
end
end

View File

@ -1,63 +0,0 @@
defmodule Friends.RelationshipsTest do
use Friends.DataCase
import Friends.FriendsFixtures
alias Friends.{Friend, Relationship}
setup do
friend1 = friend_fixture()
friend2 = friend_fixture()
%{
friend1: friend1,
friend2: friend2,
relationship: Relationship.new(
friend1, friend2
)
}
end
describe "init relationships" do
test "defaults to nothing", %{friend1: friend1, friend2: friend2} do
refute friend2 in friend1.relationships
refute friend1 in friend2.relationships
end
end
describe "types" do
test "defaults to friends", %{relationship: r} do
assert (r.type |> Relationship.types) == {
:friends, :secondary, :friend
}
assert (r |> Relationship.get_type) == :friends
assert (r |> Relationship.get_color) == :secondary
assert (r |> Relationship.get_relation) == :friend
end
end
describe "preloads" do
setup do
%{relationship: Relationship.new(
friend_fixture(), friend_fixture())
}
end
test "default nothing loaded", %{relationship: relationship} do
r = relationship
refute r.events |> Ecto.assoc_loaded?
end
test "load events", %{relationship: relationship} do
r = relationship |> Relationship.load_events
assert r.events |> Ecto.assoc_loaded?
end
test "load all", %{relationship: relationship} do
r = relationship |> Relationship.load_preloads
assert r.events |> Ecto.assoc_loaded?
end
end
end

View File

@ -1,31 +0,0 @@
defmodule FriendsWeb.FriendsControllerTest do
use FriendsWeb.ConnCase, async: true
import Friends.{AccountsFixtures, FriendsFixtures}
setup do
%{
user: _user,
friend: _friend
} = friend_fixture(%{email: user_fixture().email}) |> Friends.Friend.assign_user()
end
describe "GET '/friends'" do
test "shows the friends dashboard", %{conn: conn, friend: friend} do
conn = get(conn, "/friends")
assert html_response(conn, 200) =~ friend.name
assert html_response(conn, 200) =~ "Log in"
end
test "shows '(you)' if logged in", %{conn: conn, friend: friend, user: user} do
conn = conn |> log_in_user(user) |> get("/friends")
assert html_response(conn, 200) =~ friend.name
assert html_response(conn, 200) =~ "(you)"
assert html_response(conn, 200) =~ "Log out"
end
test "shows option to add friend if logged in", %{conn: conn, friend: friend, user: user} do
conn = conn |> log_in_user(user) |> get("/friends")
assert html_response(conn, 200) =~ Routes.friends_edit_path(conn, :overview, :new)
end
end
end

View File

@ -1,18 +0,0 @@
defmodule FriendsWeb.FriendsLiveTest do
use FriendsWeb.ConnCase, async: true
import Friends.{AccountsFixtures, FriendsFixtures}
setup do
%{
user: _user,
friend: _friend
} = friend_fixture(%{email: user_fixture().email}) |> Friends.Friend.assign_user()
end
describe "GET '/friends/:slug'" do
test "shows the friend overview", %{conn: conn, friend: friend} do
conn = conn |> get("/friends/#{friend.slug}")
assert html_response(conn, 200) =~ friend.name
end
end
end

View File

@ -1,38 +1,8 @@
defmodule FriendsWeb.PageControllerTest do defmodule FriendsWeb.PageControllerTest do
use FriendsWeb.ConnCase, async: true use FriendsWeb.ConnCase
import Friends.{AccountsFixtures, FriendsFixtures}
alias FriendsWeb.Router.Helpers, as: Routes test "GET /", %{conn: conn} do
conn = get(conn, "/")
setup do assert html_response(conn, 200) =~ "Friends App"
%{user: user_fixture()}
end
describe "GET '/'" do
test "shows the landing page if not logged in", %{conn: conn} do
conn = get(conn, "/")
assert html_response(conn, 200) =~ "Friends App"
assert html_response(conn, 200) =~ "Log in"
assert html_response(conn, 200) =~ "Register"
end
test "redirects to the new profile flow if logged in but has no profile", %{
conn: conn,
user: user
} do
_friend = friend_fixture(%{email: "random_email@invalid.biz"})
conn = conn |> log_in_user(user) |> get("/")
assert redirected_to(conn) == Routes.friends_edit_path(conn, :overview, :new)
assert get_flash(conn, :info) =~ "profile!"
end
test "redirects to the friends dashboard if logged in & has a profile", %{
conn: conn,
user: user
} do
_friend = friend_fixture(%{email: user.email})
conn = conn |> log_in_user(user) |> get("/")
assert redirected_to(conn) == Routes.friends_path(conn, :index)
end
end end
end end

View File

@ -12,10 +12,8 @@ defmodule FriendsWeb.UserRegistrationControllerTest do
assert response =~ "Register</a>" assert response =~ "Register</a>"
end end
test "redirects to dashboard if already logged in and profile loaded", %{conn: conn} do test "redirects if already logged in", %{conn: conn} do
conn = conn conn = conn |> log_in_user(user_fixture()) |> get(Routes.user_registration_path(conn, :new))
|> log_in_user(user_fixture())
|> get(Routes.user_registration_path(conn, :new))
assert redirected_to(conn) == "/" assert redirected_to(conn) == "/"
end end
end end
@ -25,21 +23,20 @@ defmodule FriendsWeb.UserRegistrationControllerTest do
test "creates account and logs the user in", %{conn: conn} do test "creates account and logs the user in", %{conn: conn} do
email = unique_user_email() email = unique_user_email()
_conn = conn =
post(conn, Routes.user_registration_path(conn, :create), %{ post(conn, Routes.user_registration_path(conn, :create), %{
"user" => valid_user_attributes(email: email) "user" => valid_user_attributes(email: email)
}) })
assert get_session(conn, :user_token)
#assert get_session(conn, :user_token) assert redirected_to(conn) == "/"
#assert redirected_to(conn) == "/"
# Now do a logged in request and assert on the menu # Now do a logged in request and assert on the menu
#conn = get(conn, "/") conn = get(conn, "/")
#response = html_response(conn, 200) response = html_response(conn, 200)
#assert response =~ email assert response =~ email
#assert response =~ "Settings</a>" assert response =~ "Settings</a>"
#assert response =~ "Log out</a>" assert response =~ "Log out</a>"
end end
test "render errors for invalid data", %{conn: conn} do test "render errors for invalid data", %{conn: conn} do
@ -51,6 +48,7 @@ defmodule FriendsWeb.UserRegistrationControllerTest do
response = html_response(conn, 200) response = html_response(conn, 200)
assert response =~ "<h1>Register</h1>" assert response =~ "<h1>Register</h1>"
assert response =~ "must have the @ sign and no spaces" assert response =~ "must have the @ sign and no spaces"
assert response =~ "should be at least 12 character"
end end
end end
end end

View File

@ -100,6 +100,7 @@ defmodule FriendsWeb.UserResetPasswordControllerTest do
response = html_response(conn, 200) response = html_response(conn, 200)
assert response =~ "<h1>Reset password</h1>" assert response =~ "<h1>Reset password</h1>"
assert response =~ "should be at least 12 character(s)"
assert response =~ "does not match password" assert response =~ "does not match password"
end end

View File

@ -1,7 +1,7 @@
defmodule FriendsWeb.UserSessionControllerTest do defmodule FriendsWeb.UserSessionControllerTest do
use FriendsWeb.ConnCase, async: true use FriendsWeb.ConnCase, async: true
import Friends.{AccountsFixtures} import Friends.AccountsFixtures
setup do setup do
%{user: user_fixture()} %{user: user_fixture()}
@ -20,25 +20,24 @@ defmodule FriendsWeb.UserSessionControllerTest do
conn = conn |> log_in_user(user) |> get(Routes.user_session_path(conn, :new)) conn = conn |> log_in_user(user) |> get(Routes.user_session_path(conn, :new))
assert redirected_to(conn) == "/" assert redirected_to(conn) == "/"
end end
end end
describe "POST /users/log_in" do describe "POST /users/log_in" do
test "logs the user in", %{conn: conn, user: user} do test "logs the user in", %{conn: conn, user: user} do
_conn = conn =
post(conn, Routes.user_session_path(conn, :create), %{ post(conn, Routes.user_session_path(conn, :create), %{
"user" => %{"email" => user.email, "password" => valid_user_password()} "user" => %{"email" => user.email, "password" => valid_user_password()}
}) })
#assert get_session(conn, :user_token) assert get_session(conn, :user_token)
#assert redirected_to(conn) == "/" assert redirected_to(conn) == "/"
# Now do a logged in request and assert on the menu # Now do a logged in request and assert on the menu
#conn = get(conn, "/") conn = get(conn, "/")
#response = html_response(conn, 200) response = html_response(conn, 200)
#assert response =~ user.email assert response =~ user.email
#assert response =~ "Settings</a>" assert response =~ "Settings</a>"
#assert response =~ "Log out</a>" assert response =~ "Log out</a>"
end end
test "logs the user in with remember me", %{conn: conn, user: user} do test "logs the user in with remember me", %{conn: conn, user: user} do

View File

@ -44,12 +44,14 @@ defmodule FriendsWeb.UserSettingsControllerTest do
"action" => "update_password", "action" => "update_password",
"current_password" => "invalid", "current_password" => "invalid",
"user" => %{ "user" => %{
"password" => "too short",
"password_confirmation" => "does not match" "password_confirmation" => "does not match"
} }
}) })
response = html_response(old_password_conn, 200) response = html_response(old_password_conn, 200)
assert response =~ "<h1>Settings</h1>" assert response =~ "<h1>Settings</h1>"
assert response =~ "should be at least 12 character(s)"
assert response =~ "does not match password" assert response =~ "does not match password"
assert response =~ "is not valid" assert response =~ "is not valid"

View File

@ -1,7 +1,3 @@
defmodule FriendsWeb.PageViewTest do defmodule FriendsWeb.PageViewTest do
use FriendsWeb.ConnCase, async: true use FriendsWeb.ConnCase, async: true
use Iamvery.Phoenix.LiveView.TestHelpers
end end

View File

@ -19,6 +19,7 @@ defmodule Friends.AccountsFixtures do
attrs attrs
|> valid_user_attributes() |> valid_user_attributes()
|> Friends.Accounts.register_user() |> Friends.Accounts.register_user()
user user
end end

View File

@ -1,34 +0,0 @@
defmodule Friends.FriendsFixtures do
@moduledoc """
This module defines test helpers for creating
entities via the `Friends.Friend` context.
"""
def random_string(length) do
:crypto.strong_rand_bytes(length)
|> Base.url_encode64
|> binary_part(0, length)
|> String.replace(~r/-/, "")
end
def unique_friend_email, do: "user#{System.unique_integer()}@example.com"
def valid_friend_name, do: "#{random_string(5)} Mc#{random_string(5)}"
def valid_friend_phone, do: "+1 (917) 624 2939" |> Helpers.format_phone
def valid_friend_birthdate, do: ~D"1990-05-05"
def valid_friend_attributes(attrs \\ %{}) do
Enum.into(attrs, %{
id: :new,
name: valid_friend_name(),
phone: valid_friend_phone(),
born: valid_friend_birthdate(),
email: unique_friend_email()
})
end
def friend_fixture(attrs \\ %{}) do
attrs
|> valid_friend_attributes()
|> Friends.Friend.create_or_update()
end
end