Uploads WIP

This commit is contained in:
Chris McCord 2021-10-31 21:19:52 -04:00
parent 2552a32865
commit da7a54a1c3
12 changed files with 182 additions and 59 deletions

View file

@ -7,8 +7,6 @@ defmodule LiveBeatsWeb.UserAuth do
alias LiveBeatsWeb.Router.Helpers, as: Routes alias LiveBeatsWeb.Router.Helpers, as: Routes
def on_mount(:current_user, _params, session, socket) do def on_mount(:current_user, _params, session, socket) do
socket = LiveView.assign(socket, :nonce, Map.fetch!(session, "nonce"))
case session do case session do
%{"user_id" => user_id} -> %{"user_id" => user_id} ->
{:cont, LiveView.assign_new(socket, :current_user, fn -> Accounts.get_user!(user_id) end)} {:cont, LiveView.assign_new(socket, :current_user, fn -> Accounts.get_user!(user_id) end)}

View file

@ -96,7 +96,7 @@ defmodule LiveBeatsWeb.LiveHelpers do
transition: {"ease-in duration-200", "opacity-100", "opacity-0"} transition: {"ease-in duration-200", "opacity-100", "opacity-0"}
) )
|> JS.hide( |> JS.hide(
to: "##{id} .modal-content", to: "##{id}-content",
transition: transition:
{"ease-in duration-200", "opacity-100 translate-y-0 sm:scale-100", {"ease-in duration-200", "opacity-100 translate-y-0 sm:scale-100",
"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"} "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"}
@ -120,7 +120,7 @@ defmodule LiveBeatsWeb.LiveHelpers do
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span> <span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<div <div
id={"#{@id}-content"} id={"#{@id}-content"}
class={"#{if @show, do: "fade-in-scale", else: "hidden"} modal-content inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6"} class={"#{if @show, do: "fade-in-scale", else: "hidden"} sticky inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform sm:my-8 sm:align-middle sm:max-w-xl sm:w-full sm:p-6"}
phx-window-keydown={hide_modal(@id)} phx-key="escape" phx-window-keydown={hide_modal(@id)} phx-key="escape"
phx-click-away={hide_modal(@id)} phx-click-away={hide_modal(@id)}
> >
@ -134,7 +134,7 @@ defmodule LiveBeatsWeb.LiveHelpers do
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg> </svg>
</div> </div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full mr-12">
<h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-title"> <h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-title">
<%= render_slot(@title) %> <%= render_slot(@title) %>
</h3> </h3>

View file

@ -1,8 +1,7 @@
defmodule LiveBeatsWeb.Nav do defmodule LiveBeatsWeb.Nav do
import Phoenix.LiveView import Phoenix.LiveView
import Phoenix.LiveView.Helpers
def on_mount(:default, _params, session, socket) do def on_mount(:default, _params, _session, socket) do
{:cont, assign(socket, genres: LiveBeats.MediaLibrary.list_genres())} {:cont, assign(socket, genres: LiveBeats.MediaLibrary.list_genres())}
end end
end end

View file

@ -20,7 +20,7 @@ defmodule LiveBeatsWeb.PlayerLive do
</div> </div>
</div> </div>
<.progress_bar nonce={@nonce} /> <.progress_bar />
<div class="text-gray-500 dark:text-gray-400 flex-row justify-between text-sm font-medium tabular-nums"> <div class="text-gray-500 dark:text-gray-400 flex-row justify-between text-sm font-medium tabular-nums">
<div><%= @time %></div> <div><%= @time %></div>

View file

@ -1,7 +1,7 @@
defmodule LiveBeatsWeb.SongLive.FormComponent do defmodule LiveBeatsWeb.SongLive.FormComponent do
use LiveBeatsWeb, :live_component use LiveBeatsWeb, :live_component
alias LiveBeats.MediaLibrary alias LiveBeats.{MediaLibrary, ID3}
@impl true @impl true
def update(%{song: song} = assigns, socket) do def update(%{song: song} = assigns, socket) do
@ -10,7 +10,14 @@ defmodule LiveBeatsWeb.SongLive.FormComponent do
{:ok, {:ok,
socket socket
|> assign(assigns) |> assign(assigns)
|> assign(:changeset, changeset)} |> assign(changeset: changeset, tmp_path: nil)
|> allow_upload(:mp3,
auto_upload: true,
progress: &handle_progress/3,
accept: ~w(.mp3),
max_entries: 1,
max_file_size: 20_000_000
)}
end end
@impl true @impl true
@ -24,6 +31,7 @@ defmodule LiveBeatsWeb.SongLive.FormComponent do
end end
def handle_event("save", %{"song" => song_params}, socket) do def handle_event("save", %{"song" => song_params}, socket) do
IO.inspect({:save, song_params})
save_song(socket, socket.assigns.action, song_params) save_song(socket, socket.assigns.action, song_params)
end end
@ -52,4 +60,43 @@ defmodule LiveBeatsWeb.SongLive.FormComponent do
{:noreply, assign(socket, changeset: changeset)} {:noreply, assign(socket, changeset: changeset)}
end end
end end
defp handle_progress(:mp3, entry, socket) do
changeset = socket.assigns.changeset
if entry.done? do
new_socket =
consume_uploaded_entry(socket, entry, fn %{} = meta ->
case ID3.parse(meta.path) do
{:ok, %ID3{} = id3} ->
new_changeset =
changeset
|> Ecto.Changeset.put_change(:title, id3.title)
|> Ecto.Changeset.put_change(:artist, id3.artist)
socket
|> assign(changeset: new_changeset)
|> put_tmp_mp3(meta.path)
{:error, _} ->
put_tmp_mp3(socket, meta.path)
end
end)
{:noreply, new_socket}
else
{:noreply, socket}
end
end
defp put_tmp_mp3(socket, path) do
if socket.assigns.tmp_path, do: File.rm!(socket.assigns.tmp_path)
{:ok, tmp_path} = Plug.Upload.random_file("temp_mp3")
File.cp!(path, tmp_path)
assign(socket, tmp_path: tmp_path)
end
defp file_error(%{kind: :too_large} = assigns), do: ~H|larger than 10MB|
defp file_error(%{kind: :not_accepted} = assigns), do: ~H|not a valid MP3 file|
end end

View file

@ -5,36 +5,127 @@
let={f} let={f}
for={@changeset} for={@changeset}
id="song-form" id="song-form"
class="space-y-8 divide-y divide-gray-200"
phx-target={@myself} phx-target={@myself}
phx-change="validate" phx-change="validate"
phx-submit="save"> phx-submit="save">
<%= label f, :album_artist %> <div class="space-y-8 divide-y divide-gray-200 sm:space-y-5">
<%= text_input f, :album_artist %> <div class="pt-8 space-y-6 sm:pt-10 sm:space-y-5">
<%= error_tag f, :album_artist %> <div class="space-y-6 sm:space-y-5">
<div class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
<%= label f, :artist %> <label for="song-form_title" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">
<%= text_input f, :artist %> Title
<%= error_tag f, :artist %> </label>
<div class="mt-1 sm:mt-0 sm:col-span-2">
<%= label f, :duration %> <%= text_input f, :title, class: "max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md" %>
<%= number_input f, :duration %> </div>
<%= error_tag f, :duration %> </div>
<%= error_tag f, :title %>
<%= label f, :title %>
<%= text_input f, :title %> <div class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
<%= error_tag f, :title %> <label for="song-form_artist" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">
Artist
<%= label f, :date_recorded %> </label>
<%= datetime_select f, :date_recorded %> <div class="mt-1 sm:mt-0 sm:col-span-2">
<%= error_tag f, :date_recorded %> <%= text_input f, :artist, class: "max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md" %>
</div>
<%= label f, :date_released %> </div>
<%= datetime_select f, :date_released %> <%= error_tag f, :artist %>
<%= error_tag f, :date_released %>
<div class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
<div> <label for="country" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">
<%= submit "Save", phx_disable_with: "Saving..." %> Genre
</label>
<div class="mt-1 sm:mt-0 sm:col-span-2">
<select id="song-form_genre_id" name="genre_id" class="max-w-lg block focus:ring-indigo-500 focus:border-indigo-500 w-full shadow-sm sm:max-w-xs sm:text-sm border-gray-300 rounded-md">
<%= for genre <- @genres do %>
<option value={genre.id}><%= genre.title %></option>
<% end %>
</select>
</div>
</div>
<!-- upload -->
<div class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
<label for="cover-photo" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">
MP3
</label>
<div class="mt-1 sm:mt-0 sm:col-span-2" phx-drop-target={@uploads.mp3.ref}>
<%= if Enum.any?(@uploads.mp3.errors) do %>
<div class="rounded-md bg-red-50 p-4 mb-2">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-red-400" x-description="Heroicon name: solid/x-circle" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"></path>
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-red-800">
Oops!
</h3>
<div class="mt-2 text-sm text-red-700">
<ul role="list" class="list-disc pl-5 space-y-1">
<%= for {_ref, error} <- @uploads.mp3.errors do %>
<li><.file_error kind={error} /></li>
<% end %>
</ul>
</div>
</div>
</div>
</div>
<% end %>
<div class="max-w-lg flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md">
<div class="space-y-1 text-center">
<svg class="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48" aria-hidden="true">
<path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
<div class="flex text-sm text-gray-600">
<label for="file-upload" class="relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500">
<span phx-click={JS.dispatch("click", to: "##{@uploads.mp3.ref}")}>Upload a file</span>
<%= live_file_input @uploads.mp3, class: "sr-only" %>
</label>
<p class="pl-1">or drag and drop</p>
</div>
<p class="text-xs text-gray-500">
MP3 up to 20MB
</p>
<%= if Enum.any?(@uploads.mp3.entries) do %>
<br/>
<%= for entry <- @uploads.mp3.entries do %>
<div class="ring-4 ring-purple-100 rounded-full">
<%= entry.client_name %>
</div>
<br/>
<div class="bg-gray-200 flex-auto rounded-full overflow-hidden">
<div id="progress"
class="bg-purple-500 dark:bg-purple-400 h-1.5 w-0"
style={"width: #{entry.progress}%;"}>
</div>
</div>
<% end %>
<% end %>
</div>
</div>
</div>
</div>
<!-- /upload -->
</div>
</div>
</div>
<div class="pt-5">
<div class="flex justify-end">
<button type="button" class="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Cancel
</button>
<button type="submit" class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Save
</button>
</div>
</div> </div>
</.form> </.form>
</div> </div>

View file

@ -23,6 +23,7 @@ defmodule LiveBeatsWeb.SongLive.Index do
action={@live_action} action={@live_action}
return_to={Routes.song_index_path(@socket, :index)} return_to={Routes.song_index_path(@socket, :index)}
song={@song} song={@song}
genres={@genres}
/> />
</.modal> </.modal>
<% end %> <% end %>

View file

@ -10,7 +10,6 @@ defmodule LiveBeatsWeb.Router do
plug :put_root_layout, {LiveBeatsWeb.LayoutView, :root} plug :put_root_layout, {LiveBeatsWeb.LayoutView, :root}
plug :protect_from_forgery plug :protect_from_forgery
plug :put_secure_browser_headers plug :put_secure_browser_headers
plug :put_nonce
end end
pipeline :api do pipeline :api do
@ -74,20 +73,4 @@ defmodule LiveBeatsWeb.Router do
forward "/mailbox", Plug.Swoosh.MailboxPreview forward "/mailbox", Plug.Swoosh.MailboxPreview
end end
end end
defp put_nonce(conn, _) do
nonce = Phoenix.HTML.Tag.csrf_token_value()
endpoint = Phoenix.Controller.endpoint_module(conn)
url = endpoint.url()
uri = endpoint.struct_url()
ws_url = %URI{uri | scheme: "ws"}
wss_url = %URI{uri | scheme: "wss"}
conn
|> put_session(:nonce, nonce)
|> put_resp_header(
"content-security-policy",
"script-src 'nonce-#{nonce}' #{url} #{ws_url}; connect-src 'self' #{ws_url} #{wss_url}"
)
end
end end

View file

@ -10,8 +10,8 @@ defmodule LiveBeatsWeb.ErrorHelpers do
""" """
def error_tag(form, field) do def error_tag(form, field) do
Enum.map(Keyword.get_values(form.errors, field), fn error -> Enum.map(Keyword.get_values(form.errors, field), fn error ->
content_tag(:span, translate_error(error), content_tag(:div, translate_error(error),
class: "invalid-feedback", class: "invalid-feedback mt-0 text-sm text-red-600 text-right",
phx_feedback_for: input_name(form, field) phx_feedback_for: input_name(form, field)
) )
end) end)

View file

@ -1,8 +1,6 @@
defmodule LiveBeatsWeb.LayoutView do defmodule LiveBeatsWeb.LayoutView do
use LiveBeatsWeb, :view use LiveBeatsWeb, :view
alias Phoenix.LiveView.JS
# Phoenix LiveDashboard is available only in development by default, # Phoenix LiveDashboard is available only in development by default,
# so we instruct Elixir to not warn if the dashboard route is missing. # so we instruct Elixir to not warn if the dashboard route is missing.
@compile {:no_warn_undefined, {Routes, :live_dashboard_path, 2}} @compile {:no_warn_undefined, {Routes, :live_dashboard_path, 2}}

View file

@ -49,7 +49,8 @@ defmodule LiveBeats.MixProject do
{:gettext, "~> 0.18"}, {:gettext, "~> 0.18"},
{:jason, "~> 1.2"}, {:jason, "~> 1.2"},
{:plug_cowboy, "~> 2.5"}, {:plug_cowboy, "~> 2.5"},
{:mint, "~> 1.0"} {:mint, "~> 1.0"},
{:erlp3tags, github: "segun/erlp3tags"}
] ]
end end

View file

@ -8,11 +8,14 @@
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
"ecto": {:hex, :ecto, "3.7.1", "a20598862351b29f80f285b21ec5297da1181c0442687f9b8329f0445d228892", [:mix], [{:decimal, "~> 1.6 or ~> 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", "d36e5b39fc479e654cffd4dbe1865d9716e4a9b6311faff799b6f90ab81b8638"}, "ecto": {:hex, :ecto, "3.7.1", "a20598862351b29f80f285b21ec5297da1181c0442687f9b8329f0445d228892", [:mix], [{:decimal, "~> 1.6 or ~> 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", "d36e5b39fc479e654cffd4dbe1865d9716e4a9b6311faff799b6f90ab81b8638"},
"ecto_sql": {:hex, :ecto_sql, "3.7.0", "2fcaad4ab0c8d76a5afbef078162806adbe709c04160aca58400d5cbbe8eeac6", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.4.0 or ~> 0.5.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a26135dfa1d99bf87a928c464cfa25bba6535a4fe761eefa56077a4febc60f70"}, "ecto_sql": {:hex, :ecto_sql, "3.7.0", "2fcaad4ab0c8d76a5afbef078162806adbe709c04160aca58400d5cbbe8eeac6", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.4.0 or ~> 0.5.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a26135dfa1d99bf87a928c464cfa25bba6535a4fe761eefa56077a4febc60f70"},
"erlog": {:git, "git://github.com/segun/erlog.git", "76825e0500b6b62b99f28411933dc15a1cb2817f", [ref: "master"]},
"erlp3tags": {:git, "https://github.com/segun/erlp3tags.git", "9b6c72da9057b9e00c7711148ce10e6cdbacae18", []},
"esbuild": {:hex, :esbuild, "0.2.2", "864b8d1cdab8aa3d08f9811af79b54d89d06dc2ec806b22b3e9bf4535579b0d5", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "0742259fded40073eb8583d38d9265b41c269ce29842e2d26b0de69b38324bf0"}, "esbuild": {:hex, :esbuild, "0.2.2", "864b8d1cdab8aa3d08f9811af79b54d89d06dc2ec806b22b3e9bf4535579b0d5", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "0742259fded40073eb8583d38d9265b41c269ce29842e2d26b0de69b38324bf0"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"floki": {:hex, :floki, "0.31.0", "f05ee8a8e6a3ced4e62beeb2c79a63bc8e12ab98fbaaf6e6a3d9b76b1278e23f", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "b05afa372f5c345a5bf240ac25ea1f0f3d5fcfd7490ac0beeb4a203f9444891e"}, "floki": {:hex, :floki, "0.31.0", "f05ee8a8e6a3ced4e62beeb2c79a63bc8e12ab98fbaaf6e6a3d9b76b1278e23f", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "b05afa372f5c345a5bf240ac25ea1f0f3d5fcfd7490ac0beeb4a203f9444891e"},
"gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"}, "gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"id3": {:hex, :id3, "1.0.1", "344840dee445c19be4b6dca9c70f0a7cd5dd03cb4260bae8ad9a21457ef12813", [:mix], [{:rustler, "~> 0.21.0", [hex: :rustler, repo: "hexpm", optional: false]}], "hexpm", "07ee2a284414065920751a528f237e77dc2aeed222a75302f909f3c07fd867e1"},
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
"mime": {:hex, :mime, "2.0.2", "0b9e1a4c840eafb68d820b0e2158ef5c49385d17fb36855ac6e7e087d4b1dcc5", [:mix], [], "hexpm", "e6a3f76b4c277739e36c2e21a2c640778ba4c3846189d5ab19f97f126df5f9b7"}, "mime": {:hex, :mime, "2.0.2", "0b9e1a4c840eafb68d820b0e2158ef5c49385d17fb36855ac6e7e087d4b1dcc5", [:mix], [], "hexpm", "e6a3f76b4c277739e36c2e21a2c640778ba4c3846189d5ab19f97f126df5f9b7"},
"mint": {:hex, :mint, "1.3.0", "396b3301102f7b775e103da5a20494b25753aed818d6d6f0ad222a3a018c3600", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "a9aac960562e43ca69a77e5176576abfa78b8398cec5543dd4fb4ab0131d5c1e"}, "mint": {:hex, :mint, "1.3.0", "396b3301102f7b775e103da5a20494b25753aed818d6d6f0ad222a3a018c3600", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "a9aac960562e43ca69a77e5176576abfa78b8398cec5543dd4fb4ab0131d5c1e"},
@ -29,8 +32,10 @@
"plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"}, "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
"postgrex": {:hex, :postgrex, "0.15.10", "2809dee1b1d76f7cbabe570b2a9285c2e7b41be60cf792f5f2804a54b838a067", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "1560ca427542f6b213f8e281633ae1a3b31cdbcd84ebd7f50628765b8f6132be"}, "postgrex": {:hex, :postgrex, "0.15.10", "2809dee1b1d76f7cbabe570b2a9285c2e7b41be60cf792f5f2804a54b838a067", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "1560ca427542f6b213f8e281633ae1a3b31cdbcd84ebd7f50628765b8f6132be"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"rustler": {:hex, :rustler, "0.22.2", "f92d6dba71bef6fe5f0d955649cb071127adc92f32a78890e8fa9939e59a1b41", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.5.2", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "56b129141e86d60a2d670af9a2b55a9071e10933ef593034565af77e84655118"},
"swoosh": {:hex, :swoosh, "1.5.0", "2be4cfc1be10f2203d1854c85b18d8c7be0321445a782efd53ef0b2b88f03ce4", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, 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]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b53891359e3ddca263ece784051243de84c9244c421a0dee1bff1d52fc5ca420"}, "swoosh": {:hex, :swoosh, "1.5.0", "2be4cfc1be10f2203d1854c85b18d8c7be0321445a782efd53ef0b2b88f03ce4", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, 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]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b53891359e3ddca263ece784051243de84c9244c421a0dee1bff1d52fc5ca420"},
"telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"}, "telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
"toml": {:hex, :toml, "0.5.2", "e471388a8726d1ce51a6b32f864b8228a1eb8edc907a0edf2bb50eab9321b526", [:mix], [], "hexpm", "f1e3dabef71fb510d015fad18c0e05e7c57281001141504c6b69d94e99750a07"},
} }