diff --git a/config/config.exs b/config/config.exs
index 27c9905..4365804 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -1,17 +1,18 @@
import Config
config :esbuild,
- version: "0.12.18",
+ version: "0.25.4",
default: [
- args: ~w(js/app.js --bundle --target=es2016 --outdir=../priv/static/assets),
+ args:
+ ~w(js/app.js --bundle --target=es2022 --outdir=../priv/static/assets/js --external:/fonts/* --external:/images/* --alias:@=.),
cd: Path.expand("../assets", __DIR__),
- env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
+ env: %{"NODE_PATH" => [Path.expand("../deps", __DIR__), Mix.Project.build_path()]}
]
config :dart_sass,
version: "1.61.0",
default: [
- args: ~w(css/app.scss ../priv/static/assets/app.css),
+ args: ~w(css/app.scss ../priv/static/assets/css/app.css),
cd: Path.expand("../assets", __DIR__)
]
@@ -27,12 +28,17 @@ config :logger, :console,
# Configures the endpoint
config :todo_app, TodoWeb.Endpoint,
+ # url: [host: "localhost", port: 10_000 + :rand.uniform(45_000)],
# because of the iOS rebind - this is now a fixed port, but randomly selected
http: [ip: {127, 0, 0, 1}, port: 10_000 + :rand.uniform(45_000)],
- render_errors: [view: TodoWeb.ErrorView, accepts: ~w(html json), layout: false],
+ adapter: Bandit.PhoenixAdapter,
+ render_errors: [
+ formats: [html: TodoWeb.ErrorHTML, json: TodoWeb.ErrorJSON],
+ layout: false
+ ],
pubsub_server: TodoApp.PubSub,
live_view: [signing_salt: "sWpG9ljX"],
- secret_key_base: :crypto.strong_rand_bytes(32),
+ secret_key_base: :crypto.strong_rand_bytes(64),
server: true
config :phoenix, :json_library, Jason
diff --git a/lib/todo_app.ex b/lib/todo_app.ex
index b649489..4834f44 100644
--- a/lib/todo_app.ex
+++ b/lib/todo_app.ex
@@ -23,14 +23,24 @@ defmodule TodoApp do
Desktop.identify_default_locale(TodoWeb.Gettext)
File.mkdir_p!(config_dir())
+ # TODO: move to runtime.exs
Application.put_env(:todo_app, TodoApp.Repo,
database: Path.join(config_dir(), "/database.sq3")
)
- {:ok, sup} = Supervisor.start_link([TodoApp.Repo], name: __MODULE__, strategy: :one_for_one)
- TodoApp.Repo.initialize()
+ :ets.new(:session, [:named_table, :public, read_concurrency: true])
- {:ok, _} = Supervisor.start_child(sup, TodoWeb.Sup)
+ {:ok, sup} = Supervisor.start_link([
+ TodoWeb.Telemetry,
+ TodoApp.Repo,
+ {DNSCluster, query: Application.get_env(:todo, :dns_cluster_query) || :ignore},
+ {Phoenix.PubSub, name: TodoApp.PubSub},
+ # Start a worker by calling: Todo.Worker.start_link(arg)
+ # {Todo.Worker, arg},
+ # Start to serve requests, typically the last entry
+ TodoWeb.Endpoint
+ ], name: __MODULE__, strategy: :one_for_one)
+ TodoApp.Repo.initialize()
{:ok, _} =
Supervisor.start_child(sup, {
diff --git a/lib/todo_app/menu_bar.ex b/lib/todo_app/menu_bar.ex
index 9756667..a3be970 100644
--- a/lib/todo_app/menu_bar.ex
+++ b/lib/todo_app/menu_bar.ex
@@ -16,15 +16,15 @@ defmodule TodoApp.MenuBar do
- <%= item.text %>
+ >{item.text}
<% end %>
- - <%= gettext "Quit" %>
+ - {gettext "Quit"}
- - <%= gettext "Show Notification" %>
- - <%= gettext "Show Observer" %>
- - <%= gettext "Open Browser" %>
+ - {gettext "Show Notification"}
+ - {gettext "Show Observer"}
+ - {gettext "Open Browser"}
"""
diff --git a/lib/todo_web.ex b/lib/todo_web.ex
index c833b99..b3cf2a5 100644
--- a/lib/todo_web.ex
+++ b/lib/todo_web.ex
@@ -17,41 +17,42 @@ defmodule TodoWeb do
and import those modules here.
"""
- def controller do
+ def static_paths, do: ~w(assets fonts images favicon.ico robots.txt)
+
+ def router do
quote do
- use Phoenix.Controller, namespace: TodoWeb
+ use Phoenix.Router, helpers: false
+ # Import common connection and controller functions to use in pipelines
import Plug.Conn
- use Gettext, backend: TodoWeb.Gettext
- alias TodoWeb.Router.Helpers, as: Routes
+ import Phoenix.Controller
+ import Phoenix.LiveView.Router
end
end
- def view do
+ def channel do
quote do
- use Phoenix.View,
- root: "lib/todo_web/templates",
- namespace: TodoWeb
+ use Phoenix.Channel
+ end
+ end
- # Import convenience functions from controllers
- import Phoenix.Controller,
- only: [view_module: 1, view_template: 1]
+ def controller do
+ quote do
+ use Phoenix.Controller, formats: [:html, :json]
+
+ use Gettext, backend: TodoWeb.Gettext
- alias Phoenix.Flash
+ import Plug.Conn
- # Include shared imports and aliases for views
- unquote(view_helpers())
- alias TodoWeb.Router.Helpers, as: Routes
+ unquote(verified_routes())
end
end
def live_view do
quote do
- use Phoenix.LiveView,
- layout: {TodoWeb.LayoutView, :live}
+ use Phoenix.LiveView
- unquote(view_helpers())
- alias TodoWeb.Router.Helpers, as: Routes
+ unquote(html_helpers())
end
end
@@ -59,42 +60,48 @@ defmodule TodoWeb do
quote do
use Phoenix.LiveComponent
- unquote(view_helpers())
- alias TodoWeb.Router.Helpers, as: Routes
+ unquote(html_helpers())
end
end
- def router do
+ def html do
quote do
- use Phoenix.Router
+ use Phoenix.Component
- import Plug.Conn
- import Phoenix.Controller
- import Phoenix.LiveView.Router
+ # Import convenience functions from controllers
+ import Phoenix.Controller,
+ only: [get_csrf_token: 0, view_module: 1, view_template: 1]
+
+ # Include general helpers for rendering HTML
+ unquote(html_helpers())
end
end
- def channel do
+ defp html_helpers do
quote do
- use Phoenix.Channel
+ # Translation
use Gettext, backend: TodoWeb.Gettext
- end
- end
- defp view_helpers do
- quote do
- # Use all HTML functionality (forms, tags, etc)
- use Phoenix.HTML
+ # HTML escaping functionality
+ import Phoenix.HTML
+ # Core UI components
+ import TodoWeb.CoreComponents
- # Import LiveView helpers (live_render, live_component, live_patch, etc)
- import Phoenix.LiveView.Helpers
- import Phoenix.Component
+ # Common modules used in templates
+ alias Phoenix.LiveView.JS
+ alias TodoWeb.Layouts
- # Import basic rendering functionality (render, render_layout, etc)
- import Phoenix.View
+ # Routes generation with the ~p sigil
+ unquote(verified_routes())
+ end
+ end
- import TodoWeb.ErrorHelpers
- use Gettext, backend: TodoWeb.Gettext
+ def verified_routes do
+ quote do
+ use Phoenix.VerifiedRoutes,
+ endpoint: TodoWeb.Endpoint,
+ router: TodoWeb.Router,
+ statics: TodoWeb.static_paths()
end
end
diff --git a/lib/todo_web/components/core_components.ex b/lib/todo_web/components/core_components.ex
new file mode 100644
index 0000000..69852d8
--- /dev/null
+++ b/lib/todo_web/components/core_components.ex
@@ -0,0 +1,472 @@
+defmodule TodoWeb.CoreComponents do
+ @moduledoc """
+ Provides core UI components.
+
+ At first glance, this module may seem daunting, but its goal is to provide
+ core building blocks for your application, such as tables, forms, and
+ inputs. The components consist mostly of markup and are well-documented
+ with doc strings and declarative assigns. You may customize and style
+ them in any way you want, based on your application growth and needs.
+
+ The foundation for styling is Tailwind CSS, a utility-first CSS framework,
+ augmented with daisyUI, a Tailwind CSS plugin that provides UI components
+ and themes. Here are useful references:
+
+ * [daisyUI](https://daisyui.com/docs/intro/) - a good place to get
+ started and see the available components.
+
+ * [Tailwind CSS](https://tailwindcss.com) - the foundational framework
+ we build on. You will use it for layout, sizing, flexbox, grid, and
+ spacing.
+
+ * [Heroicons](https://heroicons.com) - see `icon/1` for usage.
+
+ * [Phoenix.Component](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html) -
+ the component system used by Phoenix. Some components, such as `<.link>`
+ and `<.form>`, are defined there.
+
+ """
+ use Phoenix.Component
+ use Gettext, backend: TodoWeb.Gettext
+
+ alias Phoenix.LiveView.JS
+
+ @doc """
+ Renders flash notices.
+
+ ## Examples
+
+ <.flash kind={:info} flash={@flash} />
+ <.flash kind={:info} phx-mounted={show("#flash")}>Welcome Back!
+ """
+ attr :id, :string, doc: "the optional id of flash container"
+ attr :flash, :map, default: %{}, doc: "the map of flash messages to display"
+ attr :title, :string, default: nil
+ attr :kind, :atom, values: [:info, :error], doc: "used for styling and flash lookup"
+ attr :rest, :global, doc: "the arbitrary HTML attributes to add to the flash container"
+
+ slot :inner_block, doc: "the optional inner block that renders the flash message"
+
+ def flash(assigns) do
+ assigns = assign_new(assigns, :id, fn -> "flash-#{assigns.kind}" end)
+
+ ~H"""
+ hide("##{@id}")}
+ role="alert"
+ class="toast toast-top toast-end z-50"
+ {@rest}
+ >
+
+ <.icon :if={@kind == :info} name="hero-information-circle" class="size-5 shrink-0" />
+ <.icon :if={@kind == :error} name="hero-exclamation-circle" class="size-5 shrink-0" />
+
+
+
+ <.icon name="hero-x-mark" class="size-5 opacity-40 group-hover:opacity-70" />
+
+
+
+ """
+ end
+
+ @doc """
+ Renders a button with navigation support.
+
+ ## Examples
+
+ <.button>Send!
+ <.button phx-click="go" variant="primary">Send!
+ <.button navigate={~p"/"}>Home
+ """
+ attr :rest, :global, include: ~w(href navigate patch method download name value disabled)
+ attr :class, :string
+ attr :variant, :string, values: ~w(primary)
+ slot :inner_block, required: true
+
+ def button(%{rest: rest} = assigns) do
+ variants = %{"primary" => "btn-primary", nil => "btn-primary btn-soft"}
+
+ assigns =
+ assign_new(assigns, :class, fn ->
+ ["btn", Map.fetch!(variants, assigns[:variant])]
+ end)
+
+ if rest[:href] || rest[:navigate] || rest[:patch] do
+ ~H"""
+ <.link class={@class} {@rest}>
+ {render_slot(@inner_block)}
+
+ """
+ else
+ ~H"""
+
+ {render_slot(@inner_block)}
+
+ """
+ end
+ end
+
+ @doc """
+ Renders an input with label and error messages.
+
+ A `Phoenix.HTML.FormField` may be passed as argument,
+ which is used to retrieve the input name, id, and values.
+ Otherwise all attributes may be passed explicitly.
+
+ ## Types
+
+ This function accepts all HTML input types, considering that:
+
+ * You may also set `type="select"` to render a `` tag
+
+ * `type="checkbox"` is used exclusively to render boolean values
+
+ * For live file uploads, see `Phoenix.Component.live_file_input/1`
+
+ See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input
+ for more information. Unsupported types, such as hidden and radio,
+ are best written directly in your templates.
+
+ ## Examples
+
+ <.input field={@form[:email]} type="email" />
+ <.input name="my-input" errors={["oh no!"]} />
+ """
+ attr :id, :any, default: nil
+ attr :name, :any
+ attr :label, :string, default: nil
+ attr :value, :any
+
+ attr :type, :string,
+ default: "text",
+ values: ~w(checkbox color date datetime-local email file month number password
+ search select tel text textarea time url week)
+
+ attr :field, Phoenix.HTML.FormField,
+ doc: "a form field struct retrieved from the form, for example: @form[:email]"
+
+ attr :errors, :list, default: []
+ attr :checked, :boolean, doc: "the checked flag for checkbox inputs"
+ attr :prompt, :string, default: nil, doc: "the prompt for select inputs"
+ attr :options, :list, doc: "the options to pass to Phoenix.HTML.Form.options_for_select/2"
+ attr :multiple, :boolean, default: false, doc: "the multiple flag for select inputs"
+ attr :class, :string, default: nil, doc: "the input class to use over defaults"
+ attr :error_class, :string, default: nil, doc: "the input error class to use over defaults"
+
+ attr :rest, :global,
+ include: ~w(accept autocomplete capture cols disabled form list max maxlength min minlength
+ multiple pattern placeholder readonly required rows size step)
+
+ def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
+ errors = if Phoenix.Component.used_input?(field), do: field.errors, else: []
+
+ assigns
+ |> assign(field: nil, id: assigns.id || field.id)
+ |> assign(:errors, Enum.map(errors, &translate_error(&1)))
+ |> assign_new(:name, fn -> if assigns.multiple, do: field.name <> "[]", else: field.name end)
+ |> assign_new(:value, fn -> field.value end)
+ |> input()
+ end
+
+ def input(%{type: "checkbox"} = assigns) do
+ assigns =
+ assign_new(assigns, :checked, fn ->
+ Phoenix.HTML.Form.normalize_value("checkbox", assigns[:value])
+ end)
+
+ ~H"""
+
+
+
+
+ {@label}
+
+
+ <.error :for={msg <- @errors}>{msg}
+
+ """
+ end
+
+ def input(%{type: "select"} = assigns) do
+ ~H"""
+
+
+ {@label}
+
+ {@prompt}
+ {Phoenix.HTML.Form.options_for_select(@options, @value)}
+
+
+ <.error :for={msg <- @errors}>{msg}
+
+ """
+ end
+
+ def input(%{type: "textarea"} = assigns) do
+ ~H"""
+
+
+ {@label}
+
+
+ <.error :for={msg <- @errors}>{msg}
+
+ """
+ end
+
+ # All other inputs text, datetime-local, url, password, etc. are handled here...
+ def input(assigns) do
+ ~H"""
+
+
+ {@label}
+
+
+ <.error :for={msg <- @errors}>{msg}
+
+ """
+ end
+
+ # Helper used by inputs to generate form errors
+ defp error(assigns) do
+ ~H"""
+
+ <.icon name="hero-exclamation-circle" class="size-5" />
+ {render_slot(@inner_block)}
+
+ """
+ end
+
+ @doc """
+ Renders a header with title.
+ """
+ slot :inner_block, required: true
+ slot :subtitle
+ slot :actions
+
+ def header(assigns) do
+ ~H"""
+
+ """
+ end
+
+ @doc """
+ Renders a table with generic styling.
+
+ ## Examples
+
+ <.table id="users" rows={@users}>
+ <:col :let={user} label="id">{user.id}
+ <:col :let={user} label="username">{user.username}
+
+ """
+ attr :id, :string, required: true
+ attr :rows, :list, required: true
+ attr :row_id, :any, default: nil, doc: "the function for generating the row id"
+ attr :row_click, :any, default: nil, doc: "the function for handling phx-click on each row"
+
+ attr :row_item, :any,
+ default: &Function.identity/1,
+ doc: "the function for mapping each row before calling the :col and :action slots"
+
+ slot :col, required: true do
+ attr :label, :string
+ end
+
+ slot :action, doc: "the slot for showing user actions in the last table column"
+
+ def table(assigns) do
+ assigns =
+ with %{rows: %Phoenix.LiveView.LiveStream{}} <- assigns do
+ assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end)
+ end
+
+ ~H"""
+
+
+
+ {col[:label]}
+
+ {gettext("Actions")}
+
+
+
+
+
+
+ {render_slot(col, @row_item.(row))}
+
+
+
+ <%= for action <- @action do %>
+ {render_slot(action, @row_item.(row))}
+ <% end %>
+
+
+
+
+
+ """
+ end
+
+ @doc """
+ Renders a data list.
+
+ ## Examples
+
+ <.list>
+ <:item title="Title">{@post.title}
+ <:item title="Views">{@post.views}
+
+ """
+ slot :item, required: true do
+ attr :title, :string, required: true
+ end
+
+ def list(assigns) do
+ ~H"""
+
+
+
+
{item.title}
+
{render_slot(item)}
+
+
+
+ """
+ end
+
+ @doc """
+ Renders a [Heroicon](https://heroicons.com).
+
+ Heroicons come in three styles – outline, solid, and mini.
+ By default, the outline style is used, but solid and mini may
+ be applied by using the `-solid` and `-mini` suffix.
+
+ You can customize the size and colors of the icons by setting
+ width, height, and background color classes.
+
+ Icons are extracted from the `deps/heroicons` directory and bundled within
+ your compiled app.css by the plugin in `assets/vendor/heroicons.js`.
+
+ ## Examples
+
+ <.icon name="hero-x-mark" />
+ <.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" />
+ """
+ attr :name, :string, required: true
+ attr :class, :string, default: "size-4"
+
+ def icon(%{name: "hero-" <> _} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ ## JS Commands
+
+ def show(js \\ %JS{}, selector) do
+ JS.show(js,
+ to: selector,
+ time: 300,
+ transition:
+ {"transition-all ease-out duration-300",
+ "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95",
+ "opacity-100 translate-y-0 sm:scale-100"}
+ )
+ end
+
+ def hide(js \\ %JS{}, selector) do
+ JS.hide(js,
+ to: selector,
+ time: 200,
+ transition:
+ {"transition-all ease-in duration-200", "opacity-100 translate-y-0 sm:scale-100",
+ "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"}
+ )
+ end
+
+ @doc """
+ Translates an error message using gettext.
+ """
+ def translate_error({msg, opts}) do
+ # When using gettext, we typically pass the strings we want
+ # to translate as a static argument:
+ #
+ # # Translate the number of files with plural rules
+ # dngettext("errors", "1 file", "%{count} files", count)
+ #
+ # However the error messages in our forms and APIs are generated
+ # dynamically, so we need to translate them by calling Gettext
+ # with our gettext backend as first argument. Translations are
+ # available in the errors.po file (as we use the "errors" domain).
+ if count = opts[:count] do
+ Gettext.dngettext(TodoWeb.Gettext, "errors", msg, msg, count, opts)
+ else
+ Gettext.dgettext(TodoWeb.Gettext, "errors", msg, opts)
+ end
+ end
+
+ @doc """
+ Translates the errors for a field from a keyword list of errors.
+ """
+ def translate_errors(errors, field) when is_list(errors) do
+ for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts})
+ end
+end
diff --git a/lib/todo_web/components/layouts.ex b/lib/todo_web/components/layouts.ex
new file mode 100644
index 0000000..88bb626
--- /dev/null
+++ b/lib/todo_web/components/layouts.ex
@@ -0,0 +1,67 @@
+defmodule TodoWeb.Layouts do
+ @moduledoc """
+ This module holds layouts and related functionality
+ used by your application.
+ """
+ use TodoWeb, :html
+
+ # Embed all files in layouts/* within this module.
+ # The default root.html.heex file contains the HTML
+ # skeleton of your application, namely HTML headers
+ # and other static content.
+ embed_templates "layouts/*"
+
+ def app(assigns) do
+ ~H"""
+
+ <%= @inner_content %>
+ {render_slot(@inner_block)}
+
+
+ <.flash_group flash={@flash} />
+ """
+ end
+
+ @doc """
+ Shows the flash group with standard titles and content.
+
+ ## Examples
+
+ <.flash_group flash={@flash} />
+ """
+ attr :flash, :map, required: true, doc: "the map of flash messages"
+ attr :id, :string, default: "flash-group", doc: "the optional id of flash container"
+
+ def flash_group(assigns) do
+ ~H"""
+
+ <.flash kind={:info} flash={@flash} />
+ <.flash kind={:error} flash={@flash} />
+
+ <.flash
+ id="client-error"
+ kind={:error}
+ title={gettext("We can't find the internet")}
+ phx-disconnected={show(".phx-client-error #client-error") |> JS.remove_attribute("hidden")}
+ phx-connected={hide("#client-error") |> JS.set_attribute({"hidden", ""})}
+ hidden
+ >
+ {gettext("Attempting to reconnect")}
+ <.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" />
+
+
+ <.flash
+ id="server-error"
+ kind={:error}
+ title={gettext("Something went wrong!")}
+ phx-disconnected={show(".phx-server-error #server-error") |> JS.remove_attribute("hidden")}
+ phx-connected={hide("#server-error") |> JS.set_attribute({"hidden", ""})}
+ hidden
+ >
+ {gettext("Attempting to reconnect")}
+ <.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" />
+
+
+ """
+ end
+end
diff --git a/lib/todo_web/components/layouts/root.html.heex b/lib/todo_web/components/layouts/root.html.heex
new file mode 100644
index 0000000..d3c263d
--- /dev/null
+++ b/lib/todo_web/components/layouts/root.html.heex
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+ <.live_title suffix={gettext(" · Your Todos")} >
+ {assigns[:page_title] || "Todo"}
+
+
+
+
+
+
+ {@inner_content}
+
+
+
diff --git a/lib/todo_web/controllers/error.ex b/lib/todo_web/controllers/error.ex
deleted file mode 100644
index ceea2f2..0000000
--- a/lib/todo_web/controllers/error.ex
+++ /dev/null
@@ -1,11 +0,0 @@
-defmodule TodoWeb.Error do
- use TodoWeb, :controller
-
- @moduledoc """
- Error message module
- """
-
- def index(conn, _opts) do
- send_resp(conn, 401, "Unauthorized")
- end
-end
diff --git a/lib/todo_web/controllers/error_html.ex b/lib/todo_web/controllers/error_html.ex
new file mode 100644
index 0000000..de4bd18
--- /dev/null
+++ b/lib/todo_web/controllers/error_html.ex
@@ -0,0 +1,24 @@
+defmodule TodoWeb.ErrorHTML do
+ @moduledoc """
+ This module is invoked by your endpoint in case of errors on HTML requests.
+
+ See config/config.exs.
+ """
+ use TodoWeb, :html
+
+ # If you want to customize your error pages,
+ # uncomment the embed_templates/1 call below
+ # and add pages to the error directory:
+ #
+ # * lib/todo_web/controllers/error_html/404.html.heex
+ # * lib/todo_web/controllers/error_html/500.html.heex
+ #
+ # embed_templates "error_html/*"
+
+ # The default is to render a plain text page based on
+ # the template name. For example, "404.html" becomes
+ # "Not Found".
+ def render(template, _assigns) do
+ Phoenix.Controller.status_message_from_template(template)
+ end
+end
diff --git a/lib/todo_web/controllers/error_json.ex b/lib/todo_web/controllers/error_json.ex
new file mode 100644
index 0000000..ef8ebaf
--- /dev/null
+++ b/lib/todo_web/controllers/error_json.ex
@@ -0,0 +1,21 @@
+defmodule TodoWeb.ErrorJSON do
+ @moduledoc """
+ This module is invoked by your endpoint in case of errors on JSON requests.
+
+ See config/config.exs.
+ """
+
+ # If you want to customize a particular status code,
+ # you may add your own clauses, such as:
+ #
+ # def render("500.json", _assigns) do
+ # %{errors: %{detail: "Internal Server Error"}}
+ # end
+
+ # By default, Phoenix returns the status message from
+ # the template name. For example, "404.json" becomes
+ # "Not Found".
+ def render(template, _assigns) do
+ %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}}
+ end
+end
diff --git a/lib/todo_web/controllers/page_controller.ex b/lib/todo_web/controllers/page_controller.ex
new file mode 100644
index 0000000..337c24b
--- /dev/null
+++ b/lib/todo_web/controllers/page_controller.ex
@@ -0,0 +1,7 @@
+defmodule TodoWeb.PageController do
+ use TodoWeb, :controller
+
+ def home(conn, _params) do
+ render(conn, :todo)
+ end
+end
diff --git a/lib/todo_web/controllers/page_html.ex b/lib/todo_web/controllers/page_html.ex
new file mode 100644
index 0000000..df34803
--- /dev/null
+++ b/lib/todo_web/controllers/page_html.ex
@@ -0,0 +1,10 @@
+defmodule TodoWeb.PageHTML do
+ @moduledoc """
+ This module contains pages rendered by PageController.
+
+ See the `page_html` directory for all templates available.
+ """
+ use TodoWeb, :html
+
+ embed_templates "page_html/*"
+end
diff --git a/lib/todo_web/endpoint.ex b/lib/todo_web/endpoint.ex
index 1a22604..af997ba 100644
--- a/lib/todo_web/endpoint.ex
+++ b/lib/todo_web/endpoint.ex
@@ -19,8 +19,8 @@ defmodule TodoWeb.Endpoint do
plug Plug.Static,
at: "/",
from: :todo_app,
- gzip: false,
- only: ~w(assets fonts images favicon.ico robots.txt)
+ gzip: not code_reloading?,
+ only: TodoWeb.static_paths()
# Code reloading can be explicitly enabled under the
# :code_reloader configuration of your endpoint.
@@ -28,9 +28,15 @@ defmodule TodoWeb.Endpoint do
socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
plug Phoenix.LiveReloader
plug Phoenix.CodeReloader
+ plug Phoenix.Ecto.CheckRepoStatus, otp_app: :todo
end
+ plug Phoenix.LiveDashboard.RequestLogger,
+ param_key: "request_logger",
+ cookie_key: "request_logger"
+
plug Plug.RequestId
+ plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
plug Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
diff --git a/lib/todo_web/live/todo_live.ex b/lib/todo_web/live/todo_live.ex
index 408f53d..fa5231e 100644
--- a/lib/todo_web/live/todo_live.ex
+++ b/lib/todo_web/live/todo_live.ex
@@ -7,6 +7,29 @@ defmodule TodoWeb.TodoLive do
@impl true
+ def render(assigns) do
+ ~H"""
+
+
+
+ <%= for item <- @todos do %>
+ {item.text}
+ ×
+
+ <% end %>
+
+ """
+ end
+
+ @impl true
+
def mount(_args, _session, socket) do
todos = TodoApp.Todo.all_todos()
TodoApp.Todo.subscribe()
diff --git a/lib/todo_web/live/todo_live.html.leex b/lib/todo_web/live/todo_live.html.leex
deleted file mode 100644
index c87dd4d..0000000
--- a/lib/todo_web/live/todo_live.html.leex
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
- <%= for item <- @todos do %>
- <%= item.text %>
- ×
-
- <% end %>
-
diff --git a/lib/todo_web/router.ex b/lib/todo_web/router.ex
index 26a1c94..22ffdbf 100644
--- a/lib/todo_web/router.ex
+++ b/lib/todo_web/router.ex
@@ -5,7 +5,7 @@ defmodule TodoWeb.Router do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
- plug :put_root_layout, {TodoWeb.LayoutView, :root}
+ plug :put_root_layout, html: {TodoWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
end
@@ -16,6 +16,29 @@ defmodule TodoWeb.Router do
scope "/", TodoWeb do
pipe_through :browser
+
live "/", TodoLive
end
+
+ # Other scopes may use custom stacks.
+ # scope "/api", TodoWeb do
+ # pipe_through :api
+ # end
+
+ # Enable LiveDashboard and Swoosh mailbox preview in development
+ if Application.compile_env(:todo, :dev_routes) do
+ # If you want to use the LiveDashboard in production, you should put
+ # it behind authentication and allow only admins to access it.
+ # If your application does not have an admins-only section yet,
+ # you can use Plug.BasicAuth to set up some basic authentication
+ # as long as you are also using SSL (which you should anyway).
+ import Phoenix.LiveDashboard.Router
+
+ scope "/dev" do
+ pipe_through :browser
+
+ live_dashboard "/dashboard", metrics: TodoWeb.Telemetry
+ forward "/mailbox", Plug.Swoosh.MailboxPreview
+ end
+ end
end
diff --git a/lib/todo_web/sup.ex b/lib/todo_web/sup.ex
deleted file mode 100644
index 08104fa..0000000
--- a/lib/todo_web/sup.ex
+++ /dev/null
@@ -1,21 +0,0 @@
-defmodule TodoWeb.Sup do
- use Supervisor
-
- @moduledoc """
- Supervisor for the WebApp
- """
-
- def start_link([]) do
- Supervisor.start_link(__MODULE__, [], name: __MODULE__)
- end
-
- def init([]) do
- children = [
- {Phoenix.PubSub, name: TodoApp.PubSub},
- TodoWeb.Endpoint
- ]
-
- :session = :ets.new(:session, [:named_table, :public, read_concurrency: true])
- Supervisor.init(children, strategy: :one_for_one, max_restarts: 1000)
- end
-end
diff --git a/lib/todo_web/telemetry.ex b/lib/todo_web/telemetry.ex
new file mode 100644
index 0000000..9ac7aa5
--- /dev/null
+++ b/lib/todo_web/telemetry.ex
@@ -0,0 +1,93 @@
+defmodule TodoWeb.Telemetry do
+ use Supervisor
+ import Telemetry.Metrics
+
+ def start_link(arg) do
+ Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
+ end
+
+ @impl true
+ def init(_arg) do
+ children = [
+ # Telemetry poller will execute the given period measurements
+ # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics
+ {:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
+ # Add reporters as children of your supervision tree.
+ # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
+ ]
+
+ Supervisor.init(children, strategy: :one_for_one)
+ end
+
+ def metrics do
+ [
+ # Phoenix Metrics
+ summary("phoenix.endpoint.start.system_time",
+ unit: {:native, :millisecond}
+ ),
+ summary("phoenix.endpoint.stop.duration",
+ unit: {:native, :millisecond}
+ ),
+ summary("phoenix.router_dispatch.start.system_time",
+ tags: [:route],
+ unit: {:native, :millisecond}
+ ),
+ summary("phoenix.router_dispatch.exception.duration",
+ tags: [:route],
+ unit: {:native, :millisecond}
+ ),
+ summary("phoenix.router_dispatch.stop.duration",
+ tags: [:route],
+ unit: {:native, :millisecond}
+ ),
+ summary("phoenix.socket_connected.duration",
+ unit: {:native, :millisecond}
+ ),
+ sum("phoenix.socket_drain.count"),
+ summary("phoenix.channel_joined.duration",
+ unit: {:native, :millisecond}
+ ),
+ summary("phoenix.channel_handled_in.duration",
+ tags: [:event],
+ unit: {:native, :millisecond}
+ ),
+
+ # Database Metrics
+ summary("todo.repo.query.total_time",
+ unit: {:native, :millisecond},
+ description: "The sum of the other measurements"
+ ),
+ summary("todo.repo.query.decode_time",
+ unit: {:native, :millisecond},
+ description: "The time spent decoding the data received from the database"
+ ),
+ summary("todo.repo.query.query_time",
+ unit: {:native, :millisecond},
+ description: "The time spent executing the query"
+ ),
+ summary("todo.repo.query.queue_time",
+ unit: {:native, :millisecond},
+ description: "The time spent waiting for a database connection"
+ ),
+ summary("todo.repo.query.idle_time",
+ unit: {:native, :millisecond},
+ description:
+ "The time the connection spent waiting before being checked out for the query"
+ ),
+
+ # VM Metrics
+ summary("vm.memory.total", unit: {:byte, :kilobyte}),
+ summary("vm.total_run_queue_lengths.total"),
+ summary("vm.total_run_queue_lengths.cpu"),
+ summary("vm.total_run_queue_lengths.io")
+ ]
+ end
+
+ defp periodic_measurements do
+ [
+ # A module, function and arguments to be invoked periodically.
+ # This function must call :telemetry.execute/3 and a metric must be added above.
+ # {TodoWeb, :count_users, []}
+ ]
+ end
+end
diff --git a/lib/todo_web/templates/layout/app.html.eex b/lib/todo_web/templates/layout/app.html.eex
deleted file mode 100644
index f45e43f..0000000
--- a/lib/todo_web/templates/layout/app.html.eex
+++ /dev/null
@@ -1,5 +0,0 @@
-
- <%= Flash.get(@conn, :info) %>
- <%= Flash.get(@conn, :error) %>
- <%= @inner_content %>
-
diff --git a/lib/todo_web/templates/layout/live.html.leex b/lib/todo_web/templates/layout/live.html.leex
deleted file mode 100644
index 2838827..0000000
--- a/lib/todo_web/templates/layout/live.html.leex
+++ /dev/null
@@ -1,11 +0,0 @@
-
- <%= Phoenix.Flash.get(@flash, :info) %>
-
- <%= Phoenix.Flash.get(@flash, :error) %>
-
- <%= @inner_content %>
-
diff --git a/lib/todo_web/templates/layout/root.html.heex b/lib/todo_web/templates/layout/root.html.heex
deleted file mode 100644
index 84ffdb9..0000000
--- a/lib/todo_web/templates/layout/root.html.heex
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
- <%= csrf_meta_tag() %>
- <.live_title suffix={gettext(" · Your Todos")} ><%= assigns[:page_title] || "Todo" %>
-
-
-
-
-
-
- <%= @inner_content %>
-
-
-
diff --git a/lib/todo_web/views/error_helpers.ex b/lib/todo_web/views/error_helpers.ex
deleted file mode 100644
index 9d6abc8..0000000
--- a/lib/todo_web/views/error_helpers.ex
+++ /dev/null
@@ -1,51 +0,0 @@
-defmodule TodoWeb.ErrorHelpers do
- @moduledoc """
- Conveniences for translating and building error messages.
- """
-
- use Phoenix.HTML
-
- @doc """
- Generates tag for inlined form input errors.
- """
- def error_tag(form, field) do
- Enum.map(Keyword.get_values(form.errors, field), fn error ->
- content_tag(:span, translate_error(error),
- class: "invalid-feedback",
- phx_feedback_for: input_id(form, field)
- )
- end)
- end
-
- @doc """
- Translates an error message using gettext.
- """
- def translate_error(msg) when is_binary(msg) do
- translate_error({msg, []})
- end
-
- def translate_error({msg, opts}) do
- # When using gettext, we typically pass the strings we want
- # to translate as a static argument:
- #
- # # Translate "is invalid" in the "errors" domain
- # dgettext("errors", "is invalid")
- #
- # # Translate the number of files with plural rules
- # dngettext("errors", "1 file", "%{count} files", count)
- #
- # Because the error messages we show in our forms and APIs
- # are defined inside Ecto, we need to translate them dynamically.
- # This requires us to call the Gettext module passing our gettext
- # backend as first argument.
- #
- # Note we use the "errors" domain, which means translations
- # should be written to the errors.po file. The :count option is
- # set by Ecto and indicates we should also apply plural rules.
- if count = opts[:count] do
- Gettext.dngettext(TodoWeb.Gettext, "errors", msg, msg, count, opts)
- else
- Gettext.dgettext(TodoWeb.Gettext, "errors", msg, opts)
- end
- end
-end
diff --git a/lib/todo_web/views/error_view.ex b/lib/todo_web/views/error_view.ex
deleted file mode 100644
index ce12428..0000000
--- a/lib/todo_web/views/error_view.ex
+++ /dev/null
@@ -1,20 +0,0 @@
-defmodule TodoWeb.ErrorView do
- use TodoWeb, :view
-
- # If you want to customize a particular status code
- # for a certain format, you may uncomment below.
- # def render("500.html", _assigns) do
- # "Internal Server Error"
- # end
-
- def render("404.html", _assigns) do
- "Not Found"
- end
-
- # By default, Phoenix returns the status message from
- # the template name. For example, "404.html" becomes
- # "Not Found".
- def template_not_found(template, _assigns) do
- Phoenix.Controller.status_message_from_template(template)
- end
-end
diff --git a/lib/todo_web/views/layout_view.ex b/lib/todo_web/views/layout_view.ex
deleted file mode 100644
index ce43104..0000000
--- a/lib/todo_web/views/layout_view.ex
+++ /dev/null
@@ -1,3 +0,0 @@
-defmodule TodoWeb.LayoutView do
- use TodoWeb, :view
-end
diff --git a/mix.exs b/mix.exs
index 27d78bf..c8ebb29 100644
--- a/mix.exs
+++ b/mix.exs
@@ -1,14 +1,15 @@
defmodule Todo.MixProject do
use Mix.Project
- @version "1.2.0"
+ @version "1.3.0"
def project do
[
app: :todo_app,
version: @version,
- elixir: "~> 1.14",
+ elixir: "~> 1.15",
elixirc_paths: elixirc_paths(Mix.env()),
- compilers: Mix.compilers(),
+ compilers: [:phoenix_live_view] ++ Mix.compilers(),
+ listeners: [Phoenix.CodeReloader],
start_permanent: Mix.env() == :prod,
deps: deps(),
aliases: aliases(),
@@ -25,6 +26,12 @@ defmodule Todo.MixProject do
]
end
+ def cli do
+ [
+ preferred_envs: [precommit: :test]
+ ]
+ end
+
# Specifies which paths to compile per environment.
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
@@ -35,6 +42,7 @@ defmodule Todo.MixProject do
mod: {TodoApp, []},
extra_applications: [
:logger,
+ :runtime_tools,
:ssl,
:crypto,
:sasl,
@@ -75,8 +83,6 @@ defmodule Todo.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
deps_list = [
- {:ecto_sqlite3, "~> 0.12"},
- {:exqlite, github: "elixir-desktop/exqlite", override: true},
# {:desktop, path: "../desktop"},
# {:desktop, "~> 1.5"},
{:desktop, github: "elixir-desktop/desktop"},
@@ -84,17 +90,22 @@ defmodule Todo.MixProject do
# {:desktop_deployment, path: "../deployment", runtime: false},
# Phoenix
- {:phoenix, "~> 1.7"},
- {:phoenix_live_view, "~> 0.20"},
- {:phoenix_html, "~> 3.3"},
- {:phoenix_view, "~> 2.0"},
- {:phoenix_live_reload, "~> 1.5", only: [:dev]},
- {:gettext, "~> 0.23"},
- {:plug_cowboy, "~> 2.6"},
+ {:phoenix, "~> 1.8"},
+ {:phoenix_ecto, "~> 4.5"},
+ {:ecto_sqlite3, "~> 0.22"},
+ {:phoenix_html, "~> 4.1"},
+ {:phoenix_live_dashboard, "~> 0.8.3"},
+ {:phoenix_live_reload, "~> 1.2", only: :dev},
+ {:phoenix_live_view, "~> 1.1"},
+ {:telemetry_metrics, "~> 1.0"},
+ {:telemetry_poller, "~> 1.0"},
+ {:gettext, "~> 0.26"},
{:jason, "~> 1.4"},
+ {:dns_cluster, "~> 0.2.0"},
+ {:bandit, "~> 1.5"},
# Assets
- {:esbuild, "~> 0.8", runtime: Mix.env() == :dev},
+ {:esbuild, "~> 0.10", runtime: Mix.env() == :dev},
{:dart_sass, "~> 0.7", runtime: Mix.env() == :dev},
# Credo
diff --git a/mix.lock b/mix.lock
index de57354..14348a5 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,6 +1,8 @@
%{
+ "bandit": {:hex, :bandit, "1.8.0", "c2e93d7e3c5c794272fa4623124f827c6f24b643acc822be64c826f9447d92fb", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [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", "8458ff4eed20ff2a2ea69d4854883a077c33ea42b51f6811b044ceee0fa15422"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
- "castore": {:hex, :castore, "1.0.12", "053f0e32700cbec356280c0e835df425a3be4bc1e0627b714330ad9d0f05497f", [:mix], [], "hexpm", "3dca286b2186055ba0c9449b4e95b97bf1b57b47c1f2644555879e659960c224"},
+ "castore": {:hex, :castore, "1.0.15", "8aa930c890fe18b6fe0a0cff27b27d0d4d231867897bd23ea772dee561f032a3", [:mix], [], "hexpm", "96ce4c69d7d5d7a0761420ef743e2f4096253931a3ba69e5ff8ef1844fe446d3"},
+ "cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"},
"certifi": {:hex, :certifi, "2.14.0", "ed3bef654e69cde5e6c022df8070a579a79e8ba2368a00acf3d75b82d9aceeed", [:rebar3], [], "hexpm", "ea59d87ef89da429b8e905264fdec3419f84f2215bb3d81e07a18aac919026c3"},
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
"cowboy": {:hex, :cowboy, "2.13.0", "09d770dd5f6a22cc60c071f432cd7cb87776164527f205c5a6b0f24ff6b38990", [:make, :rebar3], [{:cowlib, ">= 2.14.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "e724d3a70995025d654c1992c7b11dbfea95205c047d86ff9bf1cda92ddc5614"},
@@ -8,48 +10,55 @@
"cowlib": {:hex, :cowlib, "2.14.0", "623791c56c1cc9df54a71a9c55147a401549917f00a2e48a6ae12b812c586ced", [:make, :rebar3], [], "hexpm", "0af652d1550c8411c3b58eed7a035a7fb088c0b86aff6bc504b0bc3b7f791aa2"},
"credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"},
"dart_sass": {:hex, :dart_sass, "0.7.0", "7979e056cb74fd6843e1c72db763cffc7726a9192a657735b7d24c0d9c26a1ce", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "4a8e70bca41aa00846398abdf5ad8a64d7907a0f7bf40145cd2e40d5971629f2"},
- "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.8.1", "9abdc1e68c34c6163f6fb96a96532272d13ad7ca45262156ae8b7ec6d9dc4bec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a61a3d489b239d76f326e03b98794fb8e45168396c925ef25feb405ed09da8fd"},
"dbus": {:hex, :dbus, "0.8.0", "7c800681f35d909c199265e55a8ee4aea9ebe4acccce77a0740f89f29cc57648", [:make], [], "hexpm", "a9784f2d9717ffa1f74169144a226c39633ac0d9c7fe8cb3594aeb89c827cca5"},
"debouncer": {:hex, :debouncer, "0.1.8", "ab7f6eef7c9f029929bb2b737de6dc4bf12ac4ddedb49194ba00f4240b226b5a", [:mix], [], "hexpm", "77c4be1d10f984aec0dfbc50d43c16d7512605cabe6d79cadc0c8f4ba95d4906"},
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
"desktop": {:git, "https://github.com/elixir-desktop/desktop.git", "4d5768dad0380ae0f3f4a80b0e2628c9872e1e25", []},
"desktop_deployment": {:git, "https://github.com/elixir-desktop/deployment.git", "29aa9b2de6465242b50a7b37d91b2141c505b347", []},
- "ecto": {:hex, :ecto, "3.12.5", "4a312960ce612e17337e7cefcf9be45b95a3be6b36b6f94dfb3d8c361d631866", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6eb18e80bef8bb57e17f5a7f068a1719fbda384d40fc37acb8eb8aeca493b6ea"},
- "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"},
- "ecto_sqlite3": {:hex, :ecto_sqlite3, "0.18.1", "63c4b01cbb67f5e7eecde6819bc050fbb835141b534b779f80f40d479cdce669", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.12", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:exqlite, "~> 0.22", [hex: :exqlite, repo: "hexpm", optional: false]}], "hexpm", "ce223f0eda408437e7dc2b5f2a9dab306c7a709dc865f96172ba5eadfea3ce48"},
+ "dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"},
+ "ecto": {:hex, :ecto, "3.13.3", "6a983f0917f8bdc7a89e96f2bf013f220503a0da5d8623224ba987515b3f0d80", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1927db768f53a88843ff25b6ba7946599a8ca8a055f69ad8058a1432a399af94"},
+ "ecto_sql": {:hex, :ecto_sql, "3.13.2", "a07d2461d84107b3d037097c822ffdd36ed69d1cf7c0f70e12a3d1decf04e2e1", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "539274ab0ecf1a0078a6a72ef3465629e4d6018a3028095dc90f60a19c371717"},
+ "ecto_sqlite3": {:hex, :ecto_sqlite3, "0.22.0", "edab2d0f701b7dd05dcf7e2d97769c106aff62b5cfddc000d1dd6f46b9cbd8c3", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.13.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:exqlite, "~> 0.22", [hex: :exqlite, repo: "hexpm", optional: false]}], "hexpm", "5af9e031bffcc5da0b7bca90c271a7b1e7c04a93fecf7f6cd35bc1b1921a64bd"},
"elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
- "esbuild": {:hex, :esbuild, "0.9.0", "f043eeaca4932ca8e16e5429aebd90f7766f31ac160a25cbd9befe84f2bc068f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b415027f71d5ab57ef2be844b2a10d0c1b5a492d431727f43937adce22ba45ae"},
+ "esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"},
"ex_dbus": {:hex, :ex_dbus, "0.1.4", "053df83d45b27ba0b9b6ef55a47253922069a3ace12a2a7dd30d3aff58301e17", [:mix], [{:dbus, "~> 0.8.0", [hex: :dbus, repo: "hexpm", optional: false]}, {:saxy, "~> 1.4.0", [hex: :saxy, repo: "hexpm", optional: false]}], "hexpm", "d8baeaf465eab57b70a47b70e29fdfef6eb09ba110fc37176eebe6ac7874d6d5"},
"ex_sni": {:hex, :ex_sni, "0.2.9", "81f9421035dd3edb6d69f1a4dd5f53c7071b41628130d32ba5ab7bb4bfdc2da0", [:mix], [{:debouncer, "~> 0.1", [hex: :debouncer, repo: "hexpm", optional: false]}, {:ex_dbus, "~> 0.1", [hex: :ex_dbus, repo: "hexpm", optional: false]}, {:saxy, "~> 1.4.0", [hex: :saxy, repo: "hexpm", optional: false]}], "hexpm", "921d67d913765ed20ea8354fd1798dabc957bf66990a6842d6aaa7cd5ee5bc06"},
"expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"},
- "exqlite": {:git, "https://github.com/elixir-desktop/exqlite.git", "1caf1f42395fff8a68ac5a509f7294090a6f6f0d", []},
+ "exqlite": {:hex, :exqlite, "0.33.1", "0465fdb997be174edeba6a27496fa27dfe8bc79ef1324a723daa8f0e8579da24", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "b3db0c9ae6e5ee7cf84dd0a1b6dc7566b80912eb7746d45370f5666ed66700f9"},
"file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"},
"gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"},
"hackney": {:hex, :hackney, "1.23.0", "55cc09077112bcb4a69e54be46ed9bc55537763a96cd4a80a221663a7eafd767", [:rebar3], [{:certifi, "~> 2.14.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", "6cd1c04cd15c81e5a493f167b226a15f0938a84fc8f0736ebe4ddcab65c0b44e"},
+ "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
"httpoison": {:hex, :httpoison, "2.2.2", "15420e9e5bbb505b931b2f589dc8be0c3b21e2a91a2c6ba882d99bf8f3ad499d", [:mix], [{:hackney, "~> 1.21", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "de7ac49fe2ffd89219972fdf39b268582f6f7f68d8cd29b4482dacca1ce82324"},
"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"},
"libpe": {:hex, :libpe, "1.2.3", "b0669eabc59470a63c5d2d83fe405d43e0f0de4068c5e21511e16e6948d818ee", [:mix], [], "hexpm", "494dc8082917ea645031b5ade1d8cc038548bf3d39066b2d87bebbe1bcde1c4a"},
"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.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
"mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"},
"oncrash": {:hex, :oncrash, "0.1.0", "9cf4ae8eba4ea250b579470172c5e9b8c75418b2264de7dbcf42e408d62e30fb", [:mix], [], "hexpm", "6968e775491cd857f9b6ff940bf2574fd1c2fab84fa7e14d5f56c39174c00018"},
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
- "phoenix": {:hex, :phoenix, "1.7.20", "6bababaf27d59f5628f9b608de902a021be2cecefb8231e1dbdc0a2e2e480e9b", [: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", "6be2ab98302e8784a31829e0d50d8bdfa81a23cd912c395bafd8b8bfb5a086c2"},
- "phoenix_html": {:hex, :phoenix_html, "3.3.4", "42a09fc443bbc1da37e372a5c8e6755d046f22b9b11343bf885067357da21cb3", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0249d3abec3714aff3415e7ee3d9786cb325be3151e6c4b3021502c585bf53fb"},
+ "phoenix": {:hex, :phoenix, "1.8.1", "865473a60a979551a4879db79fbfb4503e41cd809e77c85af79716578b6a456d", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {: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", "84d77d2b2e77c3c7e7527099bd01ef5c8560cd149c036d6b3a40745f11cd2fb2"},
+ "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.5", "c4ef322acd15a574a8b1a08eff0ee0a85e73096b53ce1403b6563709f15e1cea", [: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", "26ec3208eef407f31b748cadd044045c6fd485fbff168e35963d2f9dfff28d4b"},
+ "phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"},
+ "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.7", "405880012cb4b706f26dd1c6349125bfc903fb9e44d1ea668adaf4e04d4884b7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3a8625cab39ec261d48a13b7468dc619c0ede099601b084e343968309bd4d7d7"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.5.3", "f2161c207fda0e4fb55165f650f7f8db23f02b29e3bff00ff7ef161d6ac1f09d", [:mix], [{:file_system, "~> 0.3 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b4ec9cd73cb01ff1bd1cac92e045d13e7030330b74164297d1aee3907b54803c"},
- "phoenix_live_view": {:hex, :phoenix_live_view, "0.20.17", "f396bbdaf4ba227b82251eb75ac0afa6b3da5e509bc0d030206374237dfc9450", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, 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.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a61d741ffb78c85fdbca0de084da6a48f8ceb5261a79165b5a0b59e5f65ce98b"},
+ "phoenix_live_view": {:hex, :phoenix_live_view, "1.1.13", "11f48f8fbe5d7d0731d4e122a692e0f9ae8d5f98c54d573d29046833c34eada3", [:mix], [{:igniter, ">= 0.6.16 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:lazy_html, "~> 0.1.0", [hex: :lazy_html, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, 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.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9d06e93573b8419ef8ee832b4a9bfe0def10b15f7c129ea477dfa54f0136f7ec"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
"phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"},
- "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},
+ "plug": {:hex, :plug, "1.18.1", "5067f26f7745b7e31bc3368bc1a2b818b9779faa959b49c934c17730efc911cf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "57a57db70df2b422b564437d2d33cf8d33cd16339c1edb190cd11b1a3a546cc2"},
"plug_cowboy": {:hex, :plug_cowboy, "2.7.3", "1304d36752e8bdde213cea59ef424ca932910a91a07ef9f3874be709c4ddb94b", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "77c95524b2aa5364b247fa17089029e73b951ebc1adeef429361eab0bb55819d"},
- "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
+ "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"},
"ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"},
"saxy": {:hex, :saxy, "1.4.0", "c7203ad20001f72eaaad07d08f82be063fa94a40924e6bb39d93d55f979abcba", [:mix], [], "hexpm", "3fe790354d3f2234ad0b5be2d99822a23fa2d4e8ccd6657c672901dac172e9a9"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
+ "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"},
+ "telemetry_poller": {:hex, :telemetry_poller, "1.3.0", "d5c46420126b5ac2d72bc6580fb4f537d35e851cc0f8dbd571acf6d6e10f5ec7", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "51f18bed7128544a50f75897db9974436ea9bfba560420b646af27a9a9b35211"},
+ "thousand_island": {:hex, :thousand_island, "1.4.1", "8df065e627407e281f7935da5ad0f3842d10eb721afa92e760b720d71e2e37aa", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "204a8640e5d2818589b87286ae66160978628d7edf6095181cbe0440765fb6c1"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
"websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [: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", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"},