Compare commits

..
13 Commits
Author SHA1 Message Date
KKlochko 8a3c22dab1 Add the configuration for Drone CI/CD to run E2E tests.
continuous-integration/drone/push Build is failing
2024-09-06 14:32:21 +03:00
KKlochko 38e1e03b26 Add a E2E test to create new link without a random shorten. 2024-09-06 13:07:19 +03:00
KKlochko 9e22e1bfde Add the configuration for wallaby. 2024-09-06 12:54:20 +03:00
KKlochko ddf54a9264 Add wallaby and flop as dependencies. 2024-09-06 12:46:31 +03:00
KKlochko d0646b93f9 Update the form for Link to create with a random shorten if needed.
continuous-integration/drone/push Build is passing
2024-09-05 12:51:35 +03:00
KKlochko 74136c7fa7 Update the Links.create_one to support options for the generator.
continuous-integration/drone/push Build is passing
2024-09-05 12:10:27 +03:00
KKlochko 8ddb7da76e Update the generator to use options and support a string-based map. 2024-09-05 12:09:03 +03:00
KKlochko 158dcce49b Refactor the test for SafeString. 2024-09-05 12:04:13 +03:00
KKlochko 00467d249a Add tests for Links' LiveViews.
continuous-integration/drone/push Build is passing
2024-09-03 10:15:06 +03:00
KKlochko 15d12bd937 Update the show LiveView template for Links. 2024-09-03 10:10:26 +03:00
KKlochko d3c3e210d5 Add CRUD LiveViews for Links.
continuous-integration/drone/push Build is passing
2024-09-02 23:22:17 +03:00
KKlochko 1e15bc3603 Add a function to get all user's links. 2024-09-02 23:12:38 +03:00
KKlochko d2f8543fdd Update the edit function in Links to support existing links. 2024-09-02 23:10:12 +03:00
20 changed files with 591 additions and 28 deletions
+3
View File
@@ -9,6 +9,7 @@ environment:
DATABASE_HOST: postgres DATABASE_HOST: postgres
DATABASE_PORT: 5432 DATABASE_PORT: 5432
SECRET_KEY_BASE: TestSecretKeycRZ1H45gfdAsdf8DIb45df4664mRCruzwVEF68wosddfg324ost SECRET_KEY_BASE: TestSecretKeycRZ1H45gfdAsdf8DIb45df4664mRCruzwVEF68wosddfg324ost
SELENIUM_REMOTE_URL: http://standalone-firefox:4444/wd/hub/
steps: steps:
- name: install dependencies - name: install dependencies
@@ -49,4 +50,6 @@ services:
POSTGRES_DB: link_shortener_dev POSTGRES_DB: link_shortener_dev
POSTGRES_USER: postgres POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
- name: standalone-firefox
image: selenium/standalone-firefox:129.0
+11 -1
View File
@@ -21,7 +21,7 @@ config :link_shortener, LinkShortener.Repo,
config :link_shortener, LinkShortenerWeb.Endpoint, config :link_shortener, LinkShortenerWeb.Endpoint,
http: [ip: {127, 0, 0, 1}, port: 4002], http: [ip: {127, 0, 0, 1}, port: 4002],
secret_key_base: System.get_env("SECRET_KEY_BASE"), secret_key_base: System.get_env("SECRET_KEY_BASE"),
server: false server: true
# In test we don't send emails # In test we don't send emails
config :link_shortener, LinkShortener.Mailer, adapter: Swoosh.Adapters.Test config :link_shortener, LinkShortener.Mailer, adapter: Swoosh.Adapters.Test
@@ -38,3 +38,13 @@ config :phoenix, :plug_init_mode, :runtime
# Enable helpful, but potentially expensive runtime checks # Enable helpful, but potentially expensive runtime checks
config :phoenix_live_view, config :phoenix_live_view,
enable_expensive_runtime_checks: true enable_expensive_runtime_checks: true
config :wallaby,
otp_app: :link_shortener,
screenshot_on_failure: true,
screenshot_dir: "screenshots",
driver: Wallaby.Selenium,
selenium: [
remote_url: System.get_env("SELENIUM_REMOTE_URL", "http://localhost:4444/wd/hub/")
]
@@ -7,8 +7,28 @@ defmodule LinkShortener.Generators.LinkWithRandomShorten do
@doc """ @doc """
Generate a Link with random shorten with the length. Generate a Link with random shorten with the length.
## Options
• :is_atom_based (boolean, true by default) - if a map is
atom-based, then a key for shorten will be :shorten, else
"shorten".
• :length (integer, 10 by default) - the length for
the shorten.
• :generator (function) - the shorten is generated by the
generator function.
LinkShortener.Generators.SafeString.generate/1 by default.
""" """
def generate_one(attrs, length \\ 10, generator \\ &SafeString.generate/1) do @spec generate_one(map(), is_atom_based: boolean(), length: integer(), generator: function()) ::
Map.put(attrs, :shorten, generator.(length)) [map()]
def generate_one(attrs, opts \\ []) do
is_atom_based = Keyword.get(opts, :is_atom_based, true)
length = Keyword.get(opts, :length, 10)
generator = Keyword.get(opts, :generator, &SafeString.generate/1)
if is_atom_based do
Map.put(attrs, :shorten, generator.(length))
else
Map.put(attrs, "shorten", generator.(length))
end
end end
end end
+31 -4
View File
@@ -5,11 +5,32 @@ defmodule LinkShortener.Links do
alias LinkShortener.Repo alias LinkShortener.Repo
alias LinkShortener.Links.Link alias LinkShortener.Links.Link
alias LinkShortener.Generators.LinkWithRandomShorten, as: LinkGenerator alias LinkShortener.Generators.LinkWithRandomShorten, as: LinkGenerator
alias LinkShortener.Accounts.User
def new_one(), do: Link.changeset(%Link{}, %{}) def new_one(), do: Link.changeset(%Link{}, %{})
def create_one(attrs, length \\ 10, generator \\ &LinkGenerator.generate_one/2) do @doc """
generator.(attrs, length) Create a Link with random shorten with the length.
## Options
• :is_atom_based (boolean, true by default) - if a map is
atom-based, then a key for shorten will be :shorten, else
"shorten".
• :length (integer, 10 by default) - the length for
the shorten.
• :generator (function) - the shorten is generated by the
generator function.
LinkShortener.Generators.LinkWithRandomShorten.generate_one/2 by
default.
"""
@spec create_one(map(), [is_atom_based: boolean(), length: integer(), generator: function()]) ::
[map()]
def create_one(attrs, opts \\ []) do
is_atom_based = Keyword.get(opts, :is_atom_based, true)
length = Keyword.get(opts, :length, 10)
generator = Keyword.get(opts, :generator, &LinkGenerator.generate_one/2)
generator.(attrs, is_atom_based: is_atom_based, length: length)
|> insert_one() |> insert_one()
end end
@@ -49,9 +70,15 @@ defmodule LinkShortener.Links do
|> Repo.all() |> Repo.all()
end end
def edit_one(%Link{} = link) do def get_all_by_user(%User{id: user_id} = user) do
from(Link)
|> where([l], l.user_id == ^user_id)
|> Repo.all()
end
def edit_one(%Link{} = link, attrs \\ %{}) do
link link
|> Link.changeset(%{}) |> Link.changeset(attrs)
end end
def update_one(%Link{} = link, changes) do def update_one(%Link{} = link, changes) do
@@ -9,7 +9,7 @@
</a> </a>
</div> </div>
<div class="flex items-center gap-4 font-semibold leading-6 text-zinc-900"> <div class="flex items-center gap-4 font-semibold leading-6 text-zinc-900">
<a href="#" class="hover:text-zinc-700"> <a href="/links" class="hover:text-zinc-700">
Links Links
</a> </a>
</div> </div>
+1 -1
View File
@@ -12,7 +12,7 @@ defmodule LinkShortenerWeb.Endpoint do
] ]
socket "/live", Phoenix.LiveView.Socket, socket "/live", Phoenix.LiveView.Socket,
websocket: [connect_info: [session: @session_options]], websocket: [connect_info: [:user_agent, session: @session_options]],
longpoll: [connect_info: [session: @session_options]] longpoll: [connect_info: [session: @session_options]]
# Serve at "/" the static files from "priv/static" directory. # Serve at "/" the static files from "priv/static" directory.
@@ -0,0 +1,146 @@
defmodule LinkShortenerWeb.LinkLive.FormComponent do
use LinkShortenerWeb, :live_component
alias LinkShortener.Links
attr :is_checked, :boolean, default: true
@impl true
def render(assigns) do
~H"""
<div>
<.header>
<%= @title %>
<:subtitle>Use this form to manage link records.</:subtitle>
</.header>
<.simple_form
for={@form}
id="link-form"
phx-target={@myself}
phx-change="validate"
phx-submit="save"
>
<.input field={@form[:name]} type="text" label="Name" placeholder="Enter a name here" />
<.input field={@form[:url]} type="text" label="Url" placeholder="Enter an url here" />
<%= if @is_checked and @action == :new do %>
<.label>Shorten</.label>
<% end %>
<%= unless @is_checked and @action != :edit do %>
<.input
field={@form[:shorten]}
type="text"
label="Shorten"
placeholder="Enter a shorten here"
/>
<% end %>
<%= if @action == :new do %>
<label class="inline-flex items-center cursor-pointer">
<.input
id="random-toggle"
field={@form[:toggle]}
type="checkbox"
label="Random shorten"
checked={@is_checked}
phx-click="toggle"
phx-target={@myself}
phx-value-checked={!@is_checked}
class="peer sr-only"
/>
</label>
<% end %>
<:actions>
<.button phx-disable-with="Saving...">Save Link</.button>
</:actions>
</.simple_form>
</div>
"""
end
@impl true
def update(%{link: link} = assigns, socket) do
{:ok,
socket
|> assign(assigns)
|> assign_new(:form, fn ->
to_form(Links.edit_one(link))
end)}
end
@impl true
def handle_event("validate", %{"link" => link_params}, socket) do
link_params = with_user_id(link_params, socket.assigns.current_user)
changeset = Links.edit_one(socket.assigns.link, link_params)
{:noreply, assign(socket, form: to_form(changeset, action: :validate))}
end
@impl true
def handle_event("toggle", params, socket) do
is_checked = Map.get(params, "value", "false") == "true"
{:noreply, assign(socket, :is_checked, is_checked)}
end
def handle_event("save", %{"link" => link_params}, socket) do
save_link(socket, socket.assigns.action, link_params)
end
defp save_link(socket, :edit, link_params) do
case Links.update_one(socket.assigns.link, link_params) do
{:ok, link} ->
notify_parent({:saved, link})
{:noreply,
socket
|> put_flash(:info, "Link updated successfully")
|> push_patch(to: socket.assigns.patch)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, form: to_form(changeset))}
end
end
defp save_link(socket, :new, link_params) do
{is_random_string, link_params} = Map.pop(link_params, "toggle")
is_random = is_random_string == "true"
link_params =
link_params
|> with_user_id(socket.assigns.current_user)
case create_link(link_params, is_random) do
{:ok, link} ->
notify_parent({:saved, link})
{:noreply,
socket
|> put_flash(:info, "Link created successfully")
|> push_patch(to: socket.assigns.patch)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, form: to_form(changeset))}
end
end
defp create_link(link_params, is_shorten_random) do
if is_shorten_random do
Links.create_one(link_params, is_atom_based: false)
else
Links.insert_one(link_params)
end
end
defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
defp with_user_id(params, current_user) do
if Map.has_key?(params, "user_id") do
params
else
params
|> Map.put("user_id", current_user.id)
end
end
end
@@ -0,0 +1,53 @@
defmodule LinkShortenerWeb.LinkLive.Index do
use LinkShortenerWeb, :live_view
alias LinkShortener.Links
alias LinkShortener.Links.Link
@impl true
def mount(_params, _session, socket) do
current_user = socket.assigns.current_user
is_random_shorten_by_default = true
{:ok,
socket
|> stream(:links, Links.get_all_by_user(current_user))
|> assign(:is_random_shorten_by_default, is_random_shorten_by_default)}
end
@impl true
def handle_params(params, _url, socket) do
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
end
defp apply_action(socket, :edit, %{"id" => id}) do
socket
|> assign(:page_title, "Edit Link")
|> assign(:link, Links.get_one!(id))
end
defp apply_action(socket, :new, _params) do
socket
|> assign(:page_title, "New Link")
|> assign(:link, %Link{})
end
defp apply_action(socket, :index, _params) do
socket
|> assign(:page_title, "Listing Links")
|> assign(:link, nil)
end
@impl true
def handle_info({LinkShortenerWeb.LinkLive.FormComponent, {:saved, link}}, socket) do
{:noreply, stream_insert(socket, :links, link)}
end
@impl true
def handle_event("delete", %{"id" => id}, socket) do
link = Links.get_one!(id)
{:ok, _} = Links.delete_one(link)
{:noreply, stream_delete(socket, :links, link)}
end
end
@@ -0,0 +1,45 @@
<.header>
Listing Links
<:actions>
<.link patch={~p"/links/new"}>
<.button>New Link</.button>
</.link>
</:actions>
</.header>
<.table
id="links"
rows={@streams.links}
row_click={fn {_id, link} -> JS.navigate(~p"/links/#{link}") end}
>
<:col :let={{_id, link}} label="Name"><%= link.name %></:col>
<:col :let={{_id, link}} label="Url"><%= link.url %></:col>
<:col :let={{_id, link}} label="Shorten"><%= link.shorten %></:col>
<:action :let={{_id, link}}>
<div class="sr-only">
<.link navigate={~p"/links/#{link}"}>Show</.link>
</div>
<.link patch={~p"/links/#{link}/edit"}>Edit</.link>
</:action>
<:action :let={{id, link}}>
<.link
phx-click={JS.push("delete", value: %{id: link.id}) |> hide("##{id}")}
data-confirm="Are you sure?"
>
Delete
</.link>
</:action>
</.table>
<.modal :if={@live_action in [:new, :edit]} id="link-modal" show on_cancel={JS.patch(~p"/links")}>
<.live_component
module={LinkShortenerWeb.LinkLive.FormComponent}
id={@link.id || :new}
title={@page_title}
action={@live_action}
link={@link}
current_user={@current_user}
is_checked={@is_random_shorten_by_default}
patch={~p"/links"}
/>
</.modal>
@@ -0,0 +1,21 @@
defmodule LinkShortenerWeb.LinkLive.Show do
use LinkShortenerWeb, :live_view
alias LinkShortener.Links
@impl true
def mount(_params, _session, socket) do
{:ok, socket}
end
@impl true
def handle_params(%{"id" => id}, _, socket) do
{:noreply,
socket
|> assign(:page_title, page_title(socket.assigns.live_action))
|> assign(:link, Links.get_one!(id))}
end
defp page_title(:show), do: "Show Link"
defp page_title(:edit), do: "Edit Link"
end
@@ -0,0 +1,29 @@
<.header>
Link <%= @link.id %>
<:subtitle>This is a link record.</:subtitle>
<:actions>
<.link patch={~p"/links/#{@link}/show/edit"} phx-click={JS.push_focus()}>
<.button>Edit link</.button>
</.link>
</:actions>
</.header>
<.list>
<:item title="Name"><%= @link.name %></:item>
<:item title="Url"><%= @link.url %></:item>
<:item title="Shorten"><%= @link.shorten %></:item>
</.list>
<.back navigate={~p"/links"}>Back to links</.back>
<.modal :if={@live_action == :edit} id="link-modal" show on_cancel={JS.patch(~p"/links/#{@link}")}>
<.live_component
module={LinkShortenerWeb.LinkLive.FormComponent}
id={@link.id}
title={@page_title}
action={@live_action}
link={@link}
current_user={@current_user}
patch={~p"/links/#{@link}"}
/>
</.modal>
+7
View File
@@ -87,6 +87,13 @@ defmodule LinkShortenerWeb.Router do
on_mount: [{LinkShortenerWeb.UserAuth, :ensure_authenticated}] do on_mount: [{LinkShortenerWeb.UserAuth, :ensure_authenticated}] do
live "/users/settings", UserSettingsLive, :edit live "/users/settings", UserSettingsLive, :edit
live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email
live "/links", LinkLive.Index, :index
live "/links/new", LinkLive.Index, :new
live "/links/:id/edit", LinkLive.Index, :edit
live "/links/:id", LinkLive.Show, :show
live "/links/:id/show/edit", LinkLive.Show, :edit
end end
end end
+2
View File
@@ -64,6 +64,8 @@ defmodule LinkShortener.MixProject do
{:guardian_db, "~> 3.0"}, {:guardian_db, "~> 3.0"},
{:poison, "~> 5.0"}, {:poison, "~> 5.0"},
{:ex_machina, "~> 2.8.0", only: :test}, {:ex_machina, "~> 2.8.0", only: :test},
{:wallaby, "~> 0.30", runtime: false, only: :test},
{:flop, "~> 0.26.1"},
] ]
end end
+13
View File
@@ -2,6 +2,7 @@
"bandit": {:hex, :bandit, "1.5.7", "6856b1e1df4f2b0cb3df1377eab7891bec2da6a7fd69dc78594ad3e152363a50", [:mix], [{:hpax, "~> 1.0.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "f2dd92ae87d2cbea2fa9aa1652db157b6cba6c405cb44d4f6dd87abba41371cd"}, "bandit": {:hex, :bandit, "1.5.7", "6856b1e1df4f2b0cb3df1377eab7891bec2da6a7fd69dc78594ad3e152363a50", [:mix], [{:hpax, "~> 1.0.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "f2dd92ae87d2cbea2fa9aa1652db157b6cba6c405cb44d4f6dd87abba41371cd"},
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.1.0", "0b110a9a6c619b19a7f73fa3004aa11d6e719a67e672d1633dc36b6b2290a0f7", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2ad2acb5a8bc049e8d5aa267802631912bb80d5f4110a178ae7999e69dca1bf7"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.1.0", "0b110a9a6c619b19a7f73fa3004aa11d6e719a67e672d1633dc36b6b2290a0f7", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2ad2acb5a8bc049e8d5aa267802631912bb80d5f4110a178ae7999e69dca1bf7"},
"castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"}, "castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"},
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
"comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"}, "comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"},
"db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"},
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
@@ -15,17 +16,24 @@
"file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"},
"finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"},
"floki": {:hex, :floki, "0.36.2", "a7da0193538c93f937714a6704369711998a51a6164a222d710ebd54020aa7a3", [:mix], [], "hexpm", "a8766c0bc92f074e5cb36c4f9961982eda84c5d2b8e979ca67f5c268ec8ed580"}, "floki": {:hex, :floki, "0.36.2", "a7da0193538c93f937714a6704369711998a51a6164a222d710ebd54020aa7a3", [:mix], [], "hexpm", "a8766c0bc92f074e5cb36c4f9961982eda84c5d2b8e979ca67f5c268ec8ed580"},
"flop": {:hex, :flop, "0.26.1", "f0e9c6895cf876f667e9ff1c0398e53df87087fcd82d9cea8989332b9c0e1358", [:mix], [{:ecto, "~> 3.11", [hex: :ecto, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}], "hexpm", "5fcab8a1ee78111159fc4752dc9823862343b6d6bd527ff947ec1e1c27018485"},
"gettext": {:hex, :gettext, "0.26.1", "38e14ea5dcf962d1fc9f361b63ea07c0ce715a8ef1f9e82d3dfb8e67e0416715", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "01ce56f188b9dc28780a52783d6529ad2bc7124f9744e571e1ee4ea88bf08734"}, "gettext": {:hex, :gettext, "0.26.1", "38e14ea5dcf962d1fc9f361b63ea07c0ce715a8ef1f9e82d3dfb8e67e0416715", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "01ce56f188b9dc28780a52783d6529ad2bc7124f9744e571e1ee4ea88bf08734"},
"guardian": {:hex, :guardian, "2.3.2", "78003504b987f2b189d76ccf9496ceaa6a454bb2763627702233f31eb7212881", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "b189ff38cd46a22a8a824866a6867ca8722942347f13c33f7d23126af8821b52"}, "guardian": {:hex, :guardian, "2.3.2", "78003504b987f2b189d76ccf9496ceaa6a454bb2763627702233f31eb7212881", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "b189ff38cd46a22a8a824866a6867ca8722942347f13c33f7d23126af8821b52"},
"guardian_db": {:hex, :guardian_db, "3.0.0", "c42902e3f1af1ba1e2d0c10913b926a1421f3a7e38eb4fc382b715c17489abdb", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:guardian, "~> 1.0 or ~> 2.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "9c2ec4278efa34f9f1cc6ba795e552d41fdc7ffba5319d67eeb533b89392d183"}, "guardian_db": {:hex, :guardian_db, "3.0.0", "c42902e3f1af1ba1e2d0c10913b926a1421f3a7e38eb4fc382b715c17489abdb", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:guardian, "~> 1.0 or ~> 2.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "9c2ec4278efa34f9f1cc6ba795e552d41fdc7ffba5319d67eeb533b89392d183"},
"hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~>2.12.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.4.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", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"},
"heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "88ab3a0d790e6a47404cba02800a6b25d2afae50", [tag: "v2.1.1", sparse: "optimized"]}, "heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "88ab3a0d790e6a47404cba02800a6b25d2afae50", [tag: "v2.1.1", sparse: "optimized"]},
"hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"}, "hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"},
"httpoison": {:hex, :httpoison, "2.2.1", "87b7ed6d95db0389f7df02779644171d7319d319178f6680438167d7b69b1f3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "51364e6d2f429d80e14fe4b5f8e39719cacd03eb3f9a9286e61e216feac2d2df"},
"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.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"}, "jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
"mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"},
"mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"}, "mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"},
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
"phoenix": {:hex, :phoenix, "1.7.14", "a7d0b3f1bc95987044ddada111e77bd7f75646a08518942c72a8440278ae7825", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "c7859bc56cc5dfef19ecfc240775dae358cbaa530231118a9e014df392ace61a"}, "phoenix": {:hex, :phoenix, "1.7.14", "a7d0b3f1bc95987044ddada111e77bd7f75646a08518942c72a8440278ae7825", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "c7859bc56cc5dfef19ecfc240775dae358cbaa530231118a9e014df392ace61a"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.6.2", "3b83b24ab5a2eb071a20372f740d7118767c272db386831b2e77638c4dcc606d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "3f94d025f59de86be00f5f8c5dd7b5965a3298458d21ab1c328488be3b5fcd59"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.2", "3b83b24ab5a2eb071a20372f740d7118767c272db386831b2e77638c4dcc606d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "3f94d025f59de86be00f5f8c5dd7b5965a3298458d21ab1c328488be3b5fcd59"},
"phoenix_html": {:hex, :phoenix_html, "4.1.1", "4c064fd3873d12ebb1388425a8f2a19348cef56e7289e1998e2d2fa758aa982e", [:mix], [], "hexpm", "f2f2df5a72bc9a2f510b21497fd7d2b86d932ec0598f0210fed4114adc546c6f"}, "phoenix_html": {:hex, :phoenix_html, "4.1.1", "4c064fd3873d12ebb1388425a8f2a19348cef56e7289e1998e2d2fa758aa982e", [:mix], [], "hexpm", "f2f2df5a72bc9a2f510b21497fd7d2b86d932ec0598f0210fed4114adc546c6f"},
@@ -38,12 +46,17 @@
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
"poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"}, "poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"},
"postgrex": {:hex, :postgrex, "0.19.1", "73b498508b69aded53907fe48a1fee811be34cc720e69ef4ccd568c8715495ea", [:mix], [{: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", "8bac7885a18f381e091ec6caf41bda7bb8c77912bb0e9285212829afe5d8a8f8"}, "postgrex": {:hex, :postgrex, "0.19.1", "73b498508b69aded53907fe48a1fee811be34cc720e69ef4ccd568c8715495ea", [:mix], [{: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", "8bac7885a18f381e091ec6caf41bda7bb8c77912bb0e9285212829afe5d8a8f8"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
"swoosh": {:hex, :swoosh, "1.16.12", "cbb24ad512f2f7f24c7a469661c188a00a8c2cd64e0ab54acd1520f132092dfd", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0e262df1ae510d59eeaaa3db42189a2aa1b3746f73771eb2616fc3f7ee63cc20"}, "swoosh": {:hex, :swoosh, "1.16.12", "cbb24ad512f2f7f24c7a469661c188a00a8c2cd64e0ab54acd1520f132092dfd", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0e262df1ae510d59eeaaa3db42189a2aa1b3746f73771eb2616fc3f7ee63cc20"},
"tailwind": {:hex, :tailwind, "0.2.3", "277f08145d407de49650d0a4685dc062174bdd1ae7731c5f1da86163a24dfcdb", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "8e45e7a34a676a7747d04f7913a96c770c85e6be810a1d7f91e713d3a3655b5d"}, "tailwind": {:hex, :tailwind, "0.2.3", "277f08145d407de49650d0a4685dc062174bdd1ae7731c5f1da86163a24dfcdb", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "8e45e7a34a676a7747d04f7913a96c770c85e6be810a1d7f91e713d3a3655b5d"},
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
"telemetry_metrics": {:hex, :telemetry_metrics, "1.0.0", "29f5f84991ca98b8eb02fc208b2e6de7c95f8bb2294ef244a176675adc7775df", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f23713b3847286a534e005126d4c959ebcca68ae9582118ce436b521d1d47d5d"}, "telemetry_metrics": {:hex, :telemetry_metrics, "1.0.0", "29f5f84991ca98b8eb02fc208b2e6de7c95f8bb2294ef244a176675adc7775df", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f23713b3847286a534e005126d4c959ebcca68ae9582118ce436b521d1d47d5d"},
"telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"}, "telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"},
"tesla": {:hex, :tesla, "1.12.1", "fe2bf4250868ee72e5d8b8dfa408d13a00747c41b7237b6aa3b9a24057346681", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2391efc6243d37ead43afd0327b520314c7b38232091d4a440c1212626fdd6e7"},
"thousand_island": {:hex, :thousand_island, "1.3.5", "6022b6338f1635b3d32406ff98d68b843ba73b3aa95cfc27154223244f3a6ca5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2be6954916fdfe4756af3239fb6b6d75d0b8063b5df03ba76fd8a4c87849e180"}, "thousand_island": {:hex, :thousand_island, "1.3.5", "6022b6338f1635b3d32406ff98d68b843ba73b3aa95cfc27154223244f3a6ca5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2be6954916fdfe4756af3239fb6b6d75d0b8063b5df03ba76fd8a4c87849e180"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
"wallaby": {:hex, :wallaby, "0.30.9", "51d60682092c3c428c63b656b818e2258202b9f9a31ec37230659647ae20325b", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:web_driver_client, "~> 0.2.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "62e3ccb89068b231b50ed046219022020516d44f443eebef93a19db4be95b808"},
"web_driver_client": {:hex, :web_driver_client, "0.2.0", "63b76cd9eb3b0716ec5467a0f8bead73d3d9612e63f7560d21357f03ad86e31a", [:mix], [{:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:tesla, "~> 1.3", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "83cc6092bc3e74926d1c8455f0ce927d5d1d36707b74d9a65e38c084aab0350f"},
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
"websock_adapter": {:hex, :websock_adapter, "0.5.7", "65fa74042530064ef0570b75b43f5c49bb8b235d6515671b3d250022cb8a1f9e", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "d0f478ee64deddfec64b800673fd6e0c8888b079d9f3444dd96d2a98383bdbd1"}, "websock_adapter": {:hex, :websock_adapter, "0.5.7", "65fa74042530064ef0570b75b43f5c49bb8b235d6515671b3d250022cb8a1f9e", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "d0f478ee64deddfec64b800673fd6e0c8888b079d9f3444dd96d2a98383bdbd1"},
} }
@@ -10,9 +10,9 @@ defmodule LinkShortener.Generators.LinkWithRandomShortenTest do
test "generate/1 returns random safe string with 10 as length" do test "generate/1 returns random safe string with 10 as length" do
assert %{ assert %{
name: @name, name: @name,
shorten: shorten shorten: shorten
} = LinkWithRandomShorten.generate_one(@attrs) } = LinkWithRandomShorten.generate_one(@attrs)
assert String.length(shorten) == 10 assert String.length(shorten) == 10
end end
@@ -21,9 +21,13 @@ defmodule LinkShortener.Generators.LinkWithRandomShortenTest do
expected_length = 5 expected_length = 5
assert %{ assert %{
name: @name, name: @name,
shorten: shorten shorten: shorten
} = LinkWithRandomShorten.generate_one(@attrs, expected_length) } =
LinkWithRandomShorten.generate_one(@attrs,
is_atom_based: true,
length: expected_length
)
assert String.length(shorten) == expected_length assert String.length(shorten) == expected_length
end end
@@ -34,9 +38,15 @@ defmodule LinkShortener.Generators.LinkWithRandomShortenTest do
expected_length = expected_shorten |> String.length() expected_length = expected_shorten |> String.length()
assert %{ assert %{
name: @name, name: @name,
shorten: shorten shorten: shorten
} = LinkWithRandomShorten.generate_one(@attrs, expected_length, random_generator) } =
LinkWithRandomShorten.generate_one(
@attrs,
is_atom_based: true,
length: expected_length,
generator: random_generator
)
assert String.length(shorten) == expected_length assert String.length(shorten) == expected_length
assert shorten == expected_shorten assert shorten == expected_shorten
@@ -7,11 +7,10 @@ defmodule LinkShortener.Generators.SafeStringTest do
test "generate/1 returns random safe string with same length" do test "generate/1 returns random safe string with same length" do
for expected_length <- @lengths do for expected_length <- @lengths do
length = expected_length assert ^expected_length =
|> SafeString.generate() expected_length
|> String.length() |> SafeString.generate()
|> String.length()
assert length = expected_length
end end
end end
end end
+1 -1
View File
@@ -50,7 +50,7 @@ defmodule LinkShortener.LinksTest do
test "create_one/2 with valid data creates a link", %{user: user} do test "create_one/2 with valid data creates a link", %{user: user} do
assert {:ok, %Link{} = link} = assert {:ok, %Link{} = link} =
with_user(@create_generated_attrs, user) with_user(@create_generated_attrs, user)
|> Links.create_one(5) |> Links.create_one(length: 5)
assert link.name == "some link name" assert link.name == "some link name"
assert link.url == "https://gitlab.com/KKlochko/link_shortener" assert link.url == "https://gitlab.com/KKlochko/link_shortener"
@@ -0,0 +1,54 @@
defmodule LinkShortenerWeb.Features.LinkLiveTest do
use ExUnit.Case
use Wallaby.Feature
import LinkShortener.LinksFixtures
import LinkShortener.AccountsFixtures
alias LinkShortener.Factories.UserFactory
alias LinkShortener.Factories.LinkFactory
alias LinkShortener.Links
alias LinkShortener.Links.Link
@create_attrs %{name: "some name", shorten: "some shorten", url: "some url"}
@update_attrs %{name: "some updated name", shorten: "some updated shorten", url: "some updated url"}
@invalid_attrs %{name: nil, shorten: nil, url: nil}
setup %{session: session} do
user_attrs = UserFactory.user_attrs_factory()
user = user_fixture(user_attrs)
link = LinkFactory.create_link(%{user_id: user.id})
%{session: log_in_user(session, user_attrs),
user: user,
user_attrs: user_attrs,
link: link}
end
describe "Index" do
test "saves new link without a random shorten", %{session: session} do
new_link = LinkFactory.link_attrs_factory()
session
|> visit("/links")
|> click(Query.button("New Link"))
|> click(Query.checkbox("Random shorten"))
|> fill_in(Query.text_field("Name"), with: new_link.name)
|> fill_in(Query.text_field("Url"), with: new_link.url)
|> fill_in(Query.text_field("Shorten"), with: new_link.shorten)
|> click(Query.button("Save Link"))
|> assert_has(Query.xpath("//*[@id='flash-info']", text: "Link created successfully"))
assert Links.get_one_by_shorten(new_link.shorten)
end
end
def log_in_user(session, user) do
session
|> visit("/users/log_in/")
|> fill_in(Query.text_field("Email"), with: user.email)
|> fill_in(Query.text_field("Password"), with: user.password)
|> click(Query.button("Log in"))
end
end
@@ -0,0 +1,121 @@
defmodule LinkShortenerWeb.LinkLiveTest do
use LinkShortenerWeb.ConnCase
import Phoenix.LiveViewTest
import LinkShortener.LinksFixtures
import LinkShortener.AccountsFixtures
alias LinkShortener.Factories.LinkFactory
@create_attrs %{name: "some name", shorten: "some shorten", url: "some url"}
@update_attrs %{name: "some updated name", shorten: "some updated shorten", url: "some updated url"}
@invalid_attrs %{name: nil, shorten: nil, url: nil}
setup %{conn: conn} do
user = user_fixture()
link = LinkFactory.create_link(%{user_id: user.id})
%{conn: log_in_user(conn, user),
user: user,
link: link}
end
describe "Index" do
test "lists all links", %{conn: conn, link: link} do
{:ok, _index_live, html} = live(conn, ~p"/links")
assert html =~ "Listing Links"
assert html =~ link.name
end
@tag :skip
test "saves new link", %{conn: conn} do
{:ok, index_live, _html} = live(conn, ~p"/links")
assert index_live |> element("a", "New Link") |> render_click() =~
"New Link"
assert_patch(index_live, ~p"/links/new")
assert index_live
|> element("#random-toggle")
|> render_click()
assert index_live
|> form("#link-form", link: @invalid_attrs)
|> render_change() =~ "can&#39;t be blank"
assert index_live
|> form("#link-form", link: @create_attrs)
|> render_submit()
assert_patch(index_live, ~p"/links")
html = render(index_live)
assert html =~ "Link created successfully"
assert html =~ "some name"
end
test "updates link in listing", %{conn: conn, link: link} do
{:ok, index_live, _html} = live(conn, ~p"/links")
assert index_live |> element("#links-#{link.id} a", "Edit") |> render_click() =~
"Edit Link"
assert_patch(index_live, ~p"/links/#{link}/edit")
assert index_live
|> form("#link-form", link: @invalid_attrs)
|> render_change() =~ "can&#39;t be blank"
assert index_live
|> form("#link-form", link: @update_attrs)
|> render_submit()
assert_patch(index_live, ~p"/links")
html = render(index_live)
assert html =~ "Link updated successfully"
assert html =~ "some updated name"
end
test "deletes link in listing", %{conn: conn, link: link} do
{:ok, index_live, _html} = live(conn, ~p"/links")
assert index_live |> element("#links-#{link.id} a", "Delete") |> render_click()
refute has_element?(index_live, "#links-#{link.id}")
end
end
describe "Show" do
test "displays link", %{conn: conn, link: link} do
{:ok, _show_live, html} = live(conn, ~p"/links/#{link}")
assert html =~ "Show Link"
assert html =~ link.name
end
test "updates link within modal", %{conn: conn, link: link} do
{:ok, show_live, _html} = live(conn, ~p"/links/#{link}")
assert show_live |> element("a", "Edit") |> render_click() =~
"Edit Link"
assert_patch(show_live, ~p"/links/#{link}/show/edit")
assert show_live
|> form("#link-form", link: @invalid_attrs)
|> render_change() =~ "can&#39;t be blank"
assert show_live
|> form("#link-form", link: @update_attrs)
|> render_submit()
assert_patch(show_live, ~p"/links/#{link}")
html = render(show_live)
assert html =~ "Link updated successfully"
assert html =~ "some updated name"
end
end
end
+3
View File
@@ -1,2 +1,5 @@
ExUnit.start() ExUnit.start()
Ecto.Adapters.SQL.Sandbox.mode(LinkShortener.Repo, :manual) Ecto.Adapters.SQL.Sandbox.mode(LinkShortener.Repo, :manual)
{:ok, _} = Application.ensure_all_started(:wallaby)
Application.put_env(:wallaby, :base_url, LinkShortenerWeb.Endpoint.url)