Begin implenting Places using LocationIQ

This commit is contained in:
Ryan Pandya 2022-11-05 21:53:48 -07:00
parent bbf526d6a8
commit 5ca34c2b6f
14 changed files with 275 additions and 102 deletions

View File

@ -12,12 +12,12 @@ defmodule Friends.Friend do
field(:nickname, :string)
field(:born, :date)
field(:phone, :string)
field(:address, :string)
field(:email, :string)
field(:slug, :string)
field(:memories, {:array, :string})
belongs_to :user, Friends.Accounts.User, foreign_key: :user_id
belongs_to :address, Friends.Places.Place, foreign_key: :address_id
many_to_many(
:relationships,
@ -52,7 +52,7 @@ defmodule Friends.Friend do
end
def all() do
preloads = [:relationships, :reverse_relationships, :user]
preloads = [:relationships, :reverse_relationships, :user, :address]
@repo.all(from(f in Friends.Friend, preload: ^preloads))
end
@ -65,7 +65,7 @@ defmodule Friends.Friend do
@repo.one(
from(f in Friend,
where: f.slug == ^slug,
preload: [:relationships, :reverse_relationships]
preload: [:relationships, :reverse_relationships, :address]
)
)
end
@ -74,7 +74,7 @@ defmodule Friends.Friend do
@repo.one(
from(f in Friend,
where: f.id == ^id,
preload: [:relationships, :reverse_relationships]
preload: [:relationships, :reverse_relationships, :address]
)
)
end
@ -95,10 +95,9 @@ defmodule Friends.Friend do
end
def update(params) do
{:ok,
Friend.get_by_id(params.id |> String.to_integer())
|> Friend.changeset(params)
|> Map.put(:action, :update)}
|> Map.put(:action, :update)
end
def commit(changeset) do
@ -193,11 +192,12 @@ defmodule Friends.Friend do
%Friends.Friend{
relationships: %Ecto.Association.NotLoaded{},
reverse_relationships: %Ecto.Association.NotLoaded{},
user: %Ecto.Association.NotLoaded{}
user: %Ecto.Association.NotLoaded{},
address: %Ecto.Association.NotLoaded{}
} = model
) do
model
|> @repo.preload([:user, :relationships, :reverse_relationships])
|> @repo.preload([:user, :relationships, :reverse_relationships, :address])
end
def load_preloads(%Friends.Friend{} = friend), do: friend

View File

@ -0,0 +1,61 @@
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

@ -0,0 +1,27 @@
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

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

View File

@ -1,5 +1,5 @@
defmodule FriendsWeb.FriendsLive.Components do
use FriendsWeb, :live_view
use FriendsWeb, :live_component
use Phoenix.HTML
import Helpers
alias Friends.Friend
@ -25,6 +25,19 @@ defmodule FriendsWeb.FriendsLive.Components do
"""
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
@ -109,6 +122,8 @@ defmodule FriendsWeb.FriendsLive.Components do
def edit_page(:overview, assigns) do
~H"""
<%= @peer_data.address |> Tuple.to_list |> Enum.join(".") %>
<.form
for={@changeset}
let={f}
@ -155,6 +170,13 @@ defmodule FriendsWeb.FriendsLive.Components do
<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">
@ -180,7 +202,11 @@ defmodule FriendsWeb.FriendsLive.Components do
def edit_page(:relationships, assigns) do
~H"""
<%= Components.header(assigns) %>
"""
end
def edit_page(:timeline, assigns) do
~H"""
"""
end
end

View File

@ -0,0 +1,126 @@
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

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

View File

@ -28,84 +28,4 @@ defmodule FriendsWeb.FriendsLive.Friend do
|> assign_friend(friend)
|> assign(:action, Routes.friends_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,
socket
|> put_flash(:error, "Deleted '#{friend.name}'.")
|> push_navigate(to: "/")}
end
# Route to the right sub-template in Components/Components.ex
end

View File

@ -1,8 +1,8 @@
defmodule FriendsWeb.FriendsLive.Show do
use FriendsWeb, :live_view
import FriendsWeb.LiveHelpers
import FriendsWeb.FriendsLive.Components
alias Friends.Friend
alias FriendsWeb.FriendsLive.Components
def mount(%{"slug" => slug} = _attrs, token, socket) do
live_action = socket.assigns.live_action || false

View File

@ -1,12 +1,12 @@
<section class="row">
<article class="column prose">
<%= Components.menu(assigns) %>
<%= Components.header(assigns) %>
<%= Components.show_page(@live_action, assigns) %>
<%= menu(assigns) %>
<%= header(assigns) %>
<%= show_page(@live_action, assigns) %>
<%= if @editable do %>
<div class="form-control flex flex-row mb-4">
<.link patch={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, :overview, @friend.slug)} class="btn btn-block md:btn-wide text-white">edit</.link>
</div>
<% end %>
</article>

View File

@ -92,9 +92,11 @@ defmodule FriendsWeb.Router do
# View-only modes (don't require being logged in and having a profile)
scope "/friends", FriendsWeb do
pipe_through [:browser]
get "/", FriendsController, :index
end
scope "/friend", FriendsWeb do
pipe_through [:browser]
live "/:slug", FriendsLive.Show
live "/:slug/overview", FriendsLive.Show, :overview
live "/:slug/timeline", FriendsLive.Show, :timeline
@ -102,7 +104,7 @@ defmodule FriendsWeb.Router do
end
# Edit modes (require being logged in and having a profile)
scope "/update/", FriendsWeb do
scope "/friend/update/", FriendsWeb do
pipe_through [:browser, :require_authenticated_user, :capture_profile]
live "/:slug/overview", FriendsLive.Edit, :overview

View File

@ -55,7 +55,9 @@ defmodule Friends.MixProject do
{:earmark, "~> 1.4"},
{:html_sanitize_ex, "~> 1.3"},
{:yamerl, github: "yakaz/yamerl"},
{:iamvery, "~> 0.6"}
{:iamvery, "~> 0.6"},
{:httpoison, "~> 1.8"},
{:poison, "~> 5.0"}
]
end

View File

@ -22,6 +22,7 @@
"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_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"},
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
@ -41,6 +42,7 @@
"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_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"},
"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"},

View File

@ -3,7 +3,7 @@ defmodule Friends.Repo.Migrations.AddAddressesToFriends do
def change do
alter table(:friends) do
add :address, :string
add :address_id, references(:places)
end
end
end