Compare commits

..

17 Commits

Author SHA1 Message Date
Ryan Pandya
fd5bcbfc05 solo relationships, birth events, formatting 2022-11-23 17:42:49 -05:00
Ryan Pandya
5339fdb2b0 Merge branch 'dev' 2022-11-23 14:22:53 -05:00
Ryan Pandya
5ca34c2b6f Begin implenting Places using LocationIQ 2022-11-05 22:06:04 -07:00
Ryan Pandya
bbf526d6a8 Added addresses. Cleaning routes. Flow. 2022-11-05 22:06:04 -07:00
Ryan Pandya
c72c09b1b2 Small liveview changes 2022-11-05 22:06:04 -07:00
Ryan Pandya
90c3b06cfc Start relationship tests; create events 2022-11-05 22:06:04 -07:00
Ryan Pandya
d331da9e17 Happy with friend tests for now 2022-11-05 22:06:04 -07:00
Ryan Pandya
aa0c43aeb1 More frontend tests 2022-11-05 22:06:04 -07:00
Ryan Pandya
075ae78df2 Finally started writing tests 2022-11-05 22:06:04 -07:00
Ryan Pandya
1a4a7f0d0c Mucking everything up combining users/profiles 2022-11-05 22:06:04 -07:00
Ryan Pandya
f90c8621b9 Few small changes 2022-11-05 22:06:04 -07:00
Ryan Pandya
2456c6d14b Janky initial implementation of auth and profiles 2022-11-05 22:06:04 -07:00
Ryan Pandya
eccaca068d Janky initial implementation of auth and profiles 2022-11-05 22:03:46 -07:00
Ryan Pandya
0c2f304b2f Clean up forms 2022-11-05 22:01:58 -07:00
Ryan Pandya
90007d40e7 Janky initial implementation of auth and profiles 2022-10-29 17:22:34 -07:00
Ryan Pandya
2284a437fa Janky initial implementation of auth and profiles 2022-10-29 17:22:34 -07:00
Ryan Pandya
117ccf5e16 swoosh -> postmark 2022-10-29 17:22:34 -07:00
10 changed files with 273 additions and 76 deletions

View File

@ -1,5 +1,6 @@
defmodule Friends.Event do
use Ecto.Schema
import Ecto.Query
alias Friends.{Relationship, Event}
alias Places.Place
@ -16,33 +17,157 @@ defmodule Friends.Event do
belongs_to(:relationship, Relationship)
end
def changeset(event, params \\ %{}) do
event
|> Ecto.Changeset.cast(params, [
:name,
:date,
:story,
:defining,
:solo,
:place_id,
:relationship_id
])
|> Ecto.Changeset.validate_required([:name, :date, :solo, :relationship_id],
message: "This field is required."
)
|> validate_date_in_past()
|> validate_unique_event()
end
defp validate_unique_event(changeset) do
changeset
end
defp validate_date_in_past(%{changes: %{date: date}} = changeset) do
today = DateTime.utc_now() |> DateTime.to_date()
case date |> Date.diff(today) do
age when age < 0 ->
changeset |> Ecto.Changeset.add_error(:born, "Please enter a date in the past.")
_ ->
changeset
end
end
defp validate_date_in_past(changeset), do: changeset
def new(params \\ %{}) do
%Event{id: nil}
|> struct(params)
end
def get_by_id(id) do
@repo.one(
from(e in Event,
where: e.id == ^id,
preload: [:relationship, :place]
)
)
end
def get(%{relationship_id: rel, date: date, name: name}) do
@repo.one(
from(e in Event,
where: e.relationship_id == ^rel and e.date == ^date and e.name == ^name,
preload: [:relationship, :place]
)
)
end
def get_or_create(params) do
case get(params) do
nil -> create(params) |> commit()
event -> event
end
end
def create(params \\ %{id: nil}) do
Event.new(params)
|> Event.changeset()
|> Map.put(:action, :insert)
end
def update(params) do
Event.get_by_id(params.id |> String.to_integer())
|> Event.changeset(params)
|> Map.put(:action, :update)
end
def commit(changeset) do
changeset
|> @repo.commit!
|> load_preloads
end
def load_preloads(
%Event{
place: %Ecto.Association.NotLoaded{},
relationship: %Ecto.Association.NotLoaded{}
} = model
) do
model
|> @repo.preload([:place, :relationship])
end
def load_preloads(event), do: event
def create_or_update(params) do
case params.id do
"new" ->
params
|> create()
|> commit()
_number ->
params
|> update()
|> commit()
end
end
def meet(friend1, friend2, opts \\ nil) do
relationship = Relationship.get_or_new(friend1, friend2)
opts = opts ++ [if opts[:place] do
opts =
opts ++
[
if opts[:place] do
{
:place_id,
Places.Place.get_or_new(opts[:place]).id
}
end]
{:ok, event} = %Event{
end
]
{:ok, event} =
%Event{
story: opts[:story],
date: opts[:date],
place_id: opts[:place_id],
relationship_id: relationship.id
} |> @repo.insert
}
|> @repo.insert
event
end
def person(event) do
Friends.Friend.get_by_id(event.relationship.friend_id)
end
def people(event) do
event.relationship |> Relationship.members
if event.solo, do: event.person, else: event.relationship |> Relationship.members()
end
def age(event) do
years = Date.diff(Date.utc_today, event.date)
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 = Date.diff(Date.utc_today(), event.date) |> rem(365) |> div(12)
{months, :month}
else
{years, :year}
@ -52,5 +177,4 @@ defmodule Friends.Event do
def print_age(age) do
Friends.Helpers.pluralize(age |> elem(0), age |> elem(1))
end
end

View File

@ -1,7 +1,7 @@
defmodule Friends.Friend do
use Ecto.Schema
alias Friends.{Relationship, Friend}
alias Friends.{Relationship, Friend, Event}
import Helpers
import Ecto.Query
@ -46,7 +46,9 @@ defmodule Friends.Friend do
:user_id,
:address_id
])
|> Ecto.Changeset.validate_required([:name, :email, :phone, :born], message: "This field is required.")
|> Ecto.Changeset.validate_required([:name, :email, :phone, :born],
message: "This field is required."
)
|> Ecto.Changeset.validate_format(:name, ~r/\w+\ \w+/, message: "Please enter your full name.")
|> Ecto.Changeset.validate_format(
:email,
@ -64,15 +66,20 @@ defmodule Friends.Friend do
end
defp validate_birthdate(%{changes: %{born: born}} = changeset) do
today = DateTime.utc_now |> DateTime.to_date
today = DateTime.utc_now() |> DateTime.to_date()
case born |> Date.diff(today) |> div(-365) do
age when age < 0 ->
changeset |> Ecto.Changeset.add_error(:born, "Please enter a date in the past.")
age when age > 90 ->
changeset |> Ecto.Changeset.add_error(:born, "Are you sure you're #{age} years old?")
_ -> changeset
_ ->
changeset
end
end
defp validate_birthdate(changeset), do: changeset
def all() do
@ -152,7 +159,8 @@ defmodule Friends.Friend do
"new" ->
params
|> create()
|> generate_slug
|> generate_slug()
|> create_birth_event()
|> commit()
_number ->
@ -162,17 +170,17 @@ defmodule Friends.Friend do
end
end
def get_relationships(friend) do
def get_relationships(friend, include_self \\ false) do
friend
|> relations
|> relations(include_self)
|> Enum.map(&relation(friend, &1))
end
def get_events(friend) do
friend
|> get_relationships
|> Enum.map(& &1.events)
|> List.flatten()
|> get_relationships(:all)
|> Enum.flat_map(& &1.events)
|> Enum.dedup()
end
def age(friend) do
@ -250,4 +258,16 @@ defmodule Friends.Friend do
{nil, nil}
end
end
def create_birth_event(%Friend{id: id} = friend) do
solo_relationship = Relationship.get_or_new(friend, friend)
Event.get_or_create(%{
name: "Born",
date: friend.born,
solo: true,
defining: true,
relationship_id: solo_relationship.id
})
end
end

View File

@ -26,8 +26,17 @@ defmodule Friends.Places.Place do
end
def get_or_create(place) do
case @repo.one(
from(
p in Friends.Places.Place,
where: p.name == ^place.name
)
) do
nil ->
place
|> Friends.Places.Place.validate()
|> Friends.Repo.insert!()
|> @repo.insert!()
found -> found
end
end
end

View File

@ -4,6 +4,7 @@ defmodule Friends.Relationship do
alias Friends.{Relationship, Friend}
@repo Friends.Repo
@default_type 3
schema "relationships" do
field(:friend_id, :id)
@ -22,6 +23,7 @@ defmodule Friends.Relationship do
def types do
# Tuple: name of the type, associated color, and what that person "is" to the other
{
{:self, :hidden, :self},
{:acquaintances, :info, nil},
{:family, :primary, :relative},
{:friends, :secondary, :friend},
@ -68,34 +70,39 @@ defmodule Friends.Relationship do
)
end
def all() do
preloads = []
@repo.all(from(r in Friends.Relationship, preload: ^preloads))
@repo.all(from(r in Friends.Relationship, where: r.type != 0, preload: ^preloads))
end
def new(friend1, friend2, type \\ 2) do
def new(friend1, friend2, type \\ @default_type) do
id1 = friend1.id
id2 = friend2.id
{:ok, relationship} = @repo.insert(
%Relationship{
rel_type = if id1 == id2, do: 0, else: type
relationship =
@repo.insert(%Relationship{
friend_id: id1,
relation_id: id2,
type: type
}
)
type: rel_type
})
relationship
end
def get(friend1, friend2) do
id1 = friend1.id
id2 = friend2.id
rel = @repo.one(
rel =
@repo.one(
from(r in Relationship,
where: r.friend_id == ^id1 and r.relation_id == ^id2,
preload: [:events]
)
)
if rel == nil do
@repo.one(
from(r in Relationship,
@ -108,7 +115,7 @@ defmodule Friends.Relationship do
end
end
def get_or_new(a,b) do
def get_or_create(a, b) do
case get(a, b) do
nil -> new(a, b)
relationship -> relationship
@ -116,8 +123,8 @@ defmodule Friends.Relationship do
end
def get_by_slugs([slug1, slug2]) do
friend1 = slug1 |> Friend.get_by_slug
friend2 = slug2 |> Friend.get_by_slug
friend1 = slug1 |> Friend.get_by_slug()
friend2 = slug2 |> Friend.get_by_slug()
get(friend1, friend2)
end
@ -131,22 +138,33 @@ defmodule Friends.Relationship do
def age(relationship) do
relationship.events
|> Enum.map(fn(event) ->
Date.diff(Date.utc_today, event.date)
end) |> Enum.sort |> List.last |> div(365)
|> Enum.map(fn event ->
Date.diff(Date.utc_today(), event.date)
end)
|> Enum.sort()
|> List.last()
|> div(365)
end
def load_events(%Relationship{
events: %Ecto.Association.NotLoaded{}} = model) do
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
def load_preloads(
%Relationship{
events: %Ecto.Association.NotLoaded{}
} = model
) do
model
|> @repo.preload([:events])
end
def load_preloads(%Relationship{} = r), do: r
def load_preloads(%Relationship{} = r), do: r
end

View File

@ -2,6 +2,7 @@ defmodule FriendsWeb.FriendsLive.Components do
use FriendsWeb, :live_component
use Phoenix.HTML
import Helpers
import FriendsWeb.LiveHelpers
alias Friends.Friend
alias FriendsWeb.Components.{Autocomplete, Map}
alias Phoenix.LiveView.JS
@ -89,12 +90,13 @@ defmodule FriendsWeb.FriendsLive.Components do
<%= for event <- @friend |> Friends.Friend.get_events do %>
<ul>
<li>
<%= event.name %>
<b><%= event.name %></b> |
<span><%= event.date |> format_date %></span>
</li>
</ul>
<% end %>
<%= if @friend |> Friends.Friend.get_events |> Enum.empty? do %>
<div class="italic">None yet.</div>
<div class="italic">No events on record yet.</div>
<% end %>
</div>
"""
@ -102,7 +104,7 @@ defmodule FriendsWeb.FriendsLive.Components do
def show_page(:relationships, assigns) do
~H"""
<div id="relationships" class="flex md:flex-row flex-col gap-8">
<div id="relationships" class="flex md:flex-row flex-col gap-8 p-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">
@ -119,7 +121,7 @@ defmodule FriendsWeb.FriendsLive.Components do
</div>
<% end %>
<%= if @friend |> relations |> Enum.empty? do %>
<div class="italic p-4">No relationships on record yet.</div>
<div class="italic">No relationships on record yet.</div>
<% end %>
</div>
"""

View File

@ -1,7 +1,5 @@
defmodule FriendsWeb.FriendsLive.Friend do
use FriendsWeb, :live_view
alias FriendsWeb.FriendsLive.Components
alias FriendsWeb.Router.Helpers, as: Routes
alias Friends.Friend

View File

@ -22,7 +22,8 @@ defmodule FriendsWeb.FriendsLive.Show do
|> assign(:latlon, latlon |> Poison.encode!())
|> title(friend.name <> " - " <> (live_action |> titlecase))
|> assign(:changeset, %Friend{} |> Friend.changeset())
|> assign(:action, editable)}
|> assign(:editable, editable)
|> assign(:action, :moot)}
else
{:ok, socket |> redirect(to: Routes.friends_show_path(socket, :overview, friend.slug))}
end

View File

@ -6,7 +6,7 @@
<%= 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>
<.link navigate={Routes.friends_edit_path(FriendsWeb.Endpoint, @live_action, @friend.slug)} class="btn btn-block md:btn-wide text-white"><%=@live_action |> get_edit_text%></.link>
</div>
<% end %>
</article>

View File

@ -16,23 +16,44 @@ defmodule FriendsWeb.LiveHelpers do
display_phone(phone)
end
end
def display_phone(phone) do
"""
TODO: Actually implement this
"""
has_plus = phone |> String.starts_with?("+")
case phone |> String.length do
case phone |> String.length() do
10 ->
country = "+1 "
[area, first, sec1, sec2] = phone |> to_charlist |> Enum.chunk_every(3) |> Enum.map(&(&1 |> to_string))
[area, first, sec1, sec2] =
phone |> to_charlist |> Enum.chunk_every(3) |> Enum.map(&(&1 |> to_string))
"#{country}(#{area}) #{first}-#{sec1}#{sec2}"
IO.inspect "#{country}(#{area}) #{first}-#{sec1}#{sec2}"
11 when has_plus ->
phone
12 when has_plus ->
phone
end
end
def get_edit_text(live_action) do
case live_action do
:timeline -> "add a moment"
:relationships -> "add a relationship"
_ -> "edit"
end
end
def format_date(date) do
date
|> Calendar.strftime("%b %d, %Y")
end
def assign_current_user(socket, user_token) do
user =
case user_token do

View File

@ -74,16 +74,20 @@ defmodule Helpers do
birthday(friend) |> Date.diff(Date.utc_today())
end
def relations(friend) do
def relations(friend, include_self \\ false) do
list =
[friend.relationships, friend.reverse_relationships]
|> List.flatten()
|> Enum.dedup()
if include_self, do: list, else: list |> Enum.filter(&(&1.id != friend.id))
end
def relation(friend, friend2) do
Friends.Relationship.get(friend, friend2)
end
def events(relationship) do
def events(%Friends.Relationship{} = relationship) do
relationship.events
end
end