15 Commits
0.9.2 ... 0.9.3

Author SHA1 Message Date
9306d0f970 disable arm builds
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-16 21:35:37 -04:00
763c86a379 build in arm64 and amd64
Some checks are pending
continuous-integration/drone/push Build is running
2023-04-16 17:05:05 -04:00
b85b1735c0 remove maintain attrs
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-16 01:10:45 -04:00
ab1a288928 change invite path
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-16 00:46:49 -04:00
e6ef0a8c68 improve tests
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-15 21:47:50 -04:00
beeaf521c5 update npm dependencies
Some checks reported errors
continuous-integration/drone/push Build is failing
continuous-integration/drone/tag Build was killed
2023-04-14 23:56:22 -04:00
8cb6068b85 make tests async
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-14 23:48:50 -04:00
334d841d57 add pack lot number to search
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-14 23:38:28 -04:00
1037f37be2 upgrade to phoenix 1.7 2023-04-14 23:34:11 -04:00
1796fb822f fix logger errors 2023-04-14 18:25:06 -04:00
8ed64f9c87 update elixir dependencies 2023-04-14 18:20:53 -04:00
dd4a9f7119 upgrade npm dependencies 2023-04-14 18:17:11 -04:00
dbafaad500 update node and npm 2023-04-14 18:11:44 -04:00
eb4ce07b5f update erlang 2023-04-14 18:10:59 -04:00
2b7550a954 update elixir 2023-04-14 18:09:39 -04:00
157 changed files with 4889 additions and 4254 deletions

View File

@ -17,7 +17,7 @@ steps:
- .mix - .mix
- name: test - name: test
image: elixir:1.14.1-alpine image: elixir:1.14.4-alpine
environment: environment:
TEST_DATABASE_URL: ecto://postgres:postgres@database/cannery_test TEST_DATABASE_URL: ecto://postgres:postgres@database/cannery_test
HOST: testing.example.tld HOST: testing.example.tld
@ -42,7 +42,8 @@ steps:
repo: shibaobun/cannery repo: shibaobun/cannery
purge: true purge: true
compress: true compress: true
platforms: linux/amd64,linux/arm/v7 platforms:
- linux/amd64
username: username:
from_secret: docker_username from_secret: docker_username
password: password:
@ -59,7 +60,8 @@ steps:
repo: shibaobun/cannery repo: shibaobun/cannery
purge: true purge: true
compress: true compress: true
platforms: linux/amd64,linux/arm/v7 platforms:
- linux/amd64
username: username:
from_secret: docker_username from_secret: docker_username
password: password:

View File

@ -1,6 +1,6 @@
[ [
import_deps: [:ecto, :phoenix], import_deps: [:ecto, :ecto_sql, :phoenix],
inputs: ["*.{heex,ex,exs}", "priv/*/seeds.exs", "{config,lib,test}/**/*.{heex,ex,exs}"], inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}", "priv/*/seeds.exs"],
subdirectories: ["priv/*/migrations"], subdirectories: ["priv/*/migrations"],
plugins: [Phoenix.LiveView.HTMLFormatter] plugins: [Phoenix.LiveView.HTMLFormatter]
] ]

View File

@ -1,3 +1,3 @@
elixir 1.14.1-otp-25 elixir 1.14.4-otp-25
erlang 25.1.2 erlang 25.3
nodejs 18.9.1 nodejs 18.15.0

View File

@ -1,3 +1,10 @@
# v0.9.3
- Update dependencies
- Add pack lot number to search
- Improve tests
- Change invite path slightly
- Disable arm builds since ci fails to build
# v0.9.2 # v0.9.2
- Add lot number to packs - Add lot number to packs
- Don't show price paid and lot number columns when displaying packs if not used - Don't show price paid and lot number columns when displaying packs if not used

View File

@ -1,4 +1,4 @@
FROM elixir:1.14.1-alpine AS build FROM elixir:1.14.4-alpine AS build
# install build dependencies # install build dependencies
RUN apk add --no-cache build-base npm git python3 RUN apk add --no-cache build-base npm git python3

View File

@ -25,7 +25,6 @@ import 'phoenix_html'
import { Socket } from 'phoenix' import { Socket } from 'phoenix'
import { LiveSocket } from 'phoenix_live_view' import { LiveSocket } from 'phoenix_live_view'
import topbar from 'topbar' import topbar from 'topbar'
import MaintainAttrs from './maintain_attrs'
import ShotLogChart from './shot_log_chart' import ShotLogChart from './shot_log_chart'
import Date from './date' import Date from './date'
import DateTime from './datetime' import DateTime from './datetime'
@ -33,7 +32,7 @@ import DateTime from './datetime'
const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute('content') const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute('content')
const liveSocket = new LiveSocket('/live', Socket, { const liveSocket = new LiveSocket('/live', Socket, {
params: { _csrf_token: csrfToken }, params: { _csrf_token: csrfToken },
hooks: { Date, DateTime, MaintainAttrs, ShotLogChart } hooks: { Date, DateTime, ShotLogChart }
}) })
// Show progress bar on live navigation and form submits // Show progress bar on live navigation and form submits

View File

@ -1,11 +0,0 @@
// maintain user adjusted attributes, like textbox length on phoenix liveview
// update. https://github.com/phoenixframework/phoenix_live_view/issues/1011
export default {
attrs () {
const attrs = this.el.getAttribute('data-attrs')
if (attrs) { return attrs.split(', ') } else { return [] }
},
beforeUpdate () { this.prevAttrs = this.attrs().map(name => [name, this.el.getAttribute(name)]) },
updated () { this.prevAttrs.forEach(([name, val]) => this.el.setAttribute(name, val)) }
}

3569
assets/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,8 +3,8 @@
"description": " ", "description": " ",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": "v18.9.1", "node": "v18.15.0",
"npm": "8.19.1" "npm": "9.5.0"
}, },
"scripts": { "scripts": {
"deploy": "NODE_ENV=production webpack --mode production", "deploy": "NODE_ENV=production webpack --mode production",
@ -13,7 +13,7 @@
"test": "standard" "test": "standard"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.3.0", "@fortawesome/fontawesome-free": "^6.4.0",
"chart.js": "^4.2.1", "chart.js": "^4.2.1",
"chartjs-adapter-date-fns": "^3.0.0", "chartjs-adapter-date-fns": "^3.0.0",
"date-fns": "^2.29.3", "date-fns": "^2.29.3",
@ -23,27 +23,27 @@
"topbar": "^2.0.1" "topbar": "^2.0.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.21.3", "@babel/core": "^7.21.4",
"@babel/preset-env": "^7.20.2", "@babel/preset-env": "^7.21.4",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"babel-loader": "^9.1.2", "babel-loader": "^9.1.2",
"copy-webpack-plugin": "^11.0.0", "copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.3", "css-loader": "^6.7.3",
"css-minimizer-webpack-plugin": "^4.2.2", "css-minimizer-webpack-plugin": "^5.0.0",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"mini-css-extract-plugin": "^2.7.5", "mini-css-extract-plugin": "^2.7.5",
"npm-check-updates": "^16.7.12", "npm-check-updates": "^16.10.8",
"postcss": "^8.4.21", "postcss": "^8.4.21",
"postcss-import": "^15.1.0", "postcss-import": "^15.1.0",
"postcss-loader": "^7.1.0", "postcss-loader": "^7.2.4",
"postcss-preset-env": "^8.0.1", "postcss-preset-env": "^8.3.1",
"sass": "^1.59.3", "sass": "^1.62.0",
"sass-loader": "^13.2.1", "sass-loader": "^13.2.2",
"standard": "^17.0.0", "standard": "^17.0.0",
"tailwindcss": "^3.2.7", "tailwindcss": "^3.3.1",
"terser-webpack-plugin": "^5.3.7", "terser-webpack-plugin": "^5.3.7",
"webpack": "^5.76.2", "webpack": "^5.79.0",
"webpack-cli": "^5.0.1", "webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.13.1" "webpack-dev-server": "^4.13.2"
} }
} }

View File

@ -59,8 +59,7 @@ config :cannery, CanneryWeb.Endpoint,
patterns: [ patterns: [
~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$", ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
~r"priv/gettext/.*(po)$", ~r"priv/gettext/.*(po)$",
~r"lib/cannery_web/(live|views)/.*(ex)$", ~r"lib/cannery_web/*/.*(ex)$"
~r"lib/cannery_web/templates/.*(eex)$"
] ]
] ]

View File

@ -12,7 +12,7 @@ if System.get_env("PHX_SERVER") && System.get_env("RELEASE_NAME") do
config :cannery, CanneryWeb.Endpoint, server: true config :cannery, CanneryWeb.Endpoint, server: true
end end
config :cannery, CanneryWeb.ViewHelpers, shibao_mode: System.get_env("SHIBAO_MODE") == "true" config :cannery, CanneryWeb.HTMLHelpers, shibao_mode: System.get_env("SHIBAO_MODE") == "true"
# Set default locale # Set default locale
config :gettext, :default_locale, System.get_env("LOCALE", "en_US") config :gettext, :default_locale, System.get_env("LOCALE", "en_US")

View File

@ -374,8 +374,8 @@ defmodule Cannery.Accounts do
@doc """ @doc """
Deletes the signed token with the given context. Deletes the signed token with the given context.
""" """
@spec delete_session_token(token :: String.t()) :: :ok @spec delete_user_session_token(token :: String.t()) :: :ok
def delete_session_token(token) do def delete_user_session_token(token) do
UserToken.token_and_context_query(token, "session") |> Repo.delete_all() UserToken.token_and_context_query(token, "session") |> Repo.delete_all()
:ok :ok
end end

View File

@ -3,14 +3,15 @@ defmodule Cannery.Email do
Emails that can be sent using Swoosh. Emails that can be sent using Swoosh.
You can find the base email templates at You can find the base email templates at
`lib/cannery_web/templates/layout/email.html.heex` for html emails and `lib/cannery_web/components/layouts/email_html.html.heex` for html emails and
`lib/cannery_web/templates/layout/email.txt.heex` for text emails. `lib/cannery_web/components/layouts/email_text.txt.eex` for text emails.
""" """
use Phoenix.Swoosh, view: CanneryWeb.EmailView, layout: {CanneryWeb.LayoutView, :email} import Swoosh.Email
import CanneryWeb.Gettext import CanneryWeb.Gettext
import Phoenix.Template
alias Cannery.Accounts.User alias Cannery.Accounts.User
alias CanneryWeb.EmailView alias CanneryWeb.{EmailHTML, Layouts}
@typedoc """ @typedoc """
Represents an HTML and text body email that can be sent Represents an HTML and text body email that can be sent
@ -28,21 +29,33 @@ defmodule Cannery.Email do
def generate_email("welcome", user, %{"url" => url}) do def generate_email("welcome", user, %{"url" => url}) do
user user
|> base_email(dgettext("emails", "Confirm your Cannery account")) |> base_email(dgettext("emails", "Confirm your Cannery account"))
|> render_body("confirm_email.html", %{user: user, url: url}) |> html_email(:confirm_email_html, %{user: user, url: url})
|> text_body(EmailView.render("confirm_email.txt", %{user: user, url: url})) |> text_email(:confirm_email_text, %{user: user, url: url})
end end
def generate_email("reset_password", user, %{"url" => url}) do def generate_email("reset_password", user, %{"url" => url}) do
user user
|> base_email(dgettext("emails", "Reset your Cannery password")) |> base_email(dgettext("emails", "Reset your Cannery password"))
|> render_body("reset_password.html", %{user: user, url: url}) |> html_email(:reset_password_html, %{user: user, url: url})
|> text_body(EmailView.render("reset_password.txt", %{user: user, url: url})) |> text_email(:reset_password_text, %{user: user, url: url})
end end
def generate_email("update_email", user, %{"url" => url}) do def generate_email("update_email", user, %{"url" => url}) do
user user
|> base_email(dgettext("emails", "Update your Cannery email")) |> base_email(dgettext("emails", "Update your Cannery email"))
|> render_body("update_email.html", %{user: user, url: url}) |> html_email(:update_email_html, %{user: user, url: url})
|> text_body(EmailView.render("update_email.txt", %{user: user, url: url})) |> text_email(:update_email_text, %{user: user, url: url})
end
defp html_email(email, atom, assigns) do
heex = apply(EmailHTML, atom, [assigns])
html = render_to_string(Layouts, "email_html", "html", email: email, inner_content: heex)
email |> html_body(html)
end
defp text_email(email, atom, assigns) do
heex = apply(EmailHTML, atom, [assigns])
text = render_to_string(Layouts, "email_text", "text", email: email, inner_content: heex)
email |> text_body(text)
end end
end end

View File

@ -14,17 +14,17 @@ defmodule Cannery.Logger do
|> Map.put(:stacktrace, Exception.format_stacktrace(stacktrace)) |> Map.put(:stacktrace, Exception.format_stacktrace(stacktrace))
|> pretty_encode() |> pretty_encode()
Logger.error(meta.reason, data: data) Logger.error("#{meta.reason}: #{data}")
end end
def handle_event([:oban, :job, :start], measure, meta, _config) do def handle_event([:oban, :job, :start], measure, meta, _config) do
data = get_oban_job_data(meta, measure) |> pretty_encode() data = get_oban_job_data(meta, measure) |> pretty_encode()
Logger.info("Started oban job", data: data) Logger.info("Started oban job: #{data}")
end end
def handle_event([:oban, :job, :stop], measure, meta, _config) do def handle_event([:oban, :job, :stop], measure, meta, _config) do
data = get_oban_job_data(meta, measure) |> pretty_encode() data = get_oban_job_data(meta, measure) |> pretty_encode()
Logger.info("Finished oban job", data: data) Logger.info("Finished oban job: #{data}")
end end
def handle_event([:oban, :job, unhandled_event], measure, meta, _config) do def handle_event([:oban, :job, unhandled_event], measure, meta, _config) do
@ -33,7 +33,7 @@ defmodule Cannery.Logger do
|> Map.put(:event, unhandled_event) |> Map.put(:event, unhandled_event)
|> pretty_encode() |> pretty_encode()
Logger.warning("Unhandled oban job event", data: data) Logger.warning("Unhandled oban job event: #{data}")
end end
def handle_event(unhandled_event, measure, meta, config) do def handle_event(unhandled_event, measure, meta, config) do
@ -45,7 +45,7 @@ defmodule Cannery.Logger do
config: config config: config
}) })
Logger.warning("Unhandled telemetry event", data: data) Logger.warning("Unhandled telemetry event: #{data}")
end end
defp get_oban_job_data(%{job: job}, measure) do defp get_oban_job_data(%{job: job}, measure) do

View File

@ -1,53 +1,61 @@
defmodule CanneryWeb do defmodule CanneryWeb do
@moduledoc """ @moduledoc """
The entrypoint for defining your web interface, such The entrypoint for defining your web interface, such
as controllers, views, channels and so on. as controllers, components, channels, and so on.
This can be used in your application as: This can be used in your application as:
use CanneryWeb, :controller use CanneryWeb, :controller
use CanneryWeb, :view use CanneryWeb, :html
The definitions below will be executed for every view, The definitions below will be executed for every controller,
controller, etc, so keep them short and clean, focused component, etc, so keep them short and clean, focused
on imports, uses and aliases. on imports, uses and aliases.
Do NOT define functions inside the quoted expressions Do NOT define functions inside the quoted expressions
below. Instead, define any helper function in modules below. Instead, define additional modules and import
and import those modules here. those modules here.
""" """
def static_paths, do: ~w(css js fonts images favicon.ico robots.txt)
def router do
quote do
use Phoenix.Router, helpers: false
# Import common connection and controller functions to use in pipelines
import Plug.Conn
import Phoenix.Controller
import Phoenix.LiveView.Router
end
end
def channel do
quote do
use Phoenix.Channel
end
end
def controller do def controller do
quote do quote do
use Phoenix.Controller, namespace: CanneryWeb use Phoenix.Controller,
formats: [:html, :json],
layouts: [html: CanneryWeb.Layouts]
# credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
import Plug.Conn import Plug.Conn
import CanneryWeb.Gettext import CanneryWeb.Gettext
alias CanneryWeb.Router.Helpers, as: Routes
end
end
def view do unquote(verified_routes())
quote do
use Phoenix.View,
root: "lib/cannery_web/templates",
namespace: CanneryWeb
# Import convenience functions from controllers
import Phoenix.Controller,
only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1]
# Include shared imports and aliases for views
unquote(view_helpers())
end end
end end
def live_view do def live_view do
quote do quote do
use Phoenix.LiveView, layout: {CanneryWeb.LayoutView, :live} use Phoenix.LiveView,
layout: {CanneryWeb.Layouts, :app}
on_mount CanneryWeb.InitAssigns unquote(html_helpers())
unquote(view_helpers())
end end
end end
@ -55,49 +63,46 @@ defmodule CanneryWeb do
quote do quote do
use Phoenix.LiveComponent use Phoenix.LiveComponent
unquote(view_helpers()) unquote(html_helpers())
end end
end end
def component do def html do
quote do quote do
use Phoenix.Component use Phoenix.Component
unquote(view_helpers()) # 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
end end
def router do defp html_helpers do
quote do quote do
use Phoenix.Router
import Phoenix.{Controller, LiveView.Router}
# credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
import Plug.Conn
end
end
def channel do
quote do
use Phoenix.Channel
# credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
import CanneryWeb.Gettext
end
end
defp view_helpers do
quote do
# Use all HTML functionality (forms, tags, etc)
# credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
use Phoenix.HTML use Phoenix.HTML
# Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc) # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
# Import basic rendering functionality (render, render_layout, etc) import Phoenix.Component
import CanneryWeb.{ErrorHelpers, Gettext, CoreComponents, ViewHelpers} import CanneryWeb.{ErrorHelpers, Gettext, CoreComponents, HTMLHelpers}
import Phoenix.{Component, View}
alias CanneryWeb.Endpoint # Shortcut for generating JS commands
alias CanneryWeb.Router.Helpers, as: Routes alias Phoenix.LiveView.JS
# Routes generation with the ~p sigil
unquote(verified_routes())
end
end
def verified_routes do
quote do
use Phoenix.VerifiedRoutes,
endpoint: CanneryWeb.Endpoint,
router: CanneryWeb.Router,
statics: CanneryWeb.static_paths()
end end
end end

View File

@ -41,7 +41,6 @@
class: "input input-primary col-span-2", class: "input input-primary col-span-2",
maxlength: 255, maxlength: 255,
placeholder: gettext("Really great weather"), placeholder: gettext("Really great weather"),
phx_hook: "MaintainAttrs",
phx_update: "ignore" phx_update: "ignore"
) %> ) %>
<%= error_tag(f, :notes, "col-span-3") %> <%= error_tag(f, :notes, "col-span-3") %>

View File

@ -109,14 +109,12 @@ defmodule CanneryWeb.Components.ContainerTableComponent do
end end
@spec get_value_for_key(atom(), Container.t(), extra_data :: map) :: any() @spec get_value_for_key(atom(), Container.t(), extra_data :: map) :: any()
defp get_value_for_key(:name, %{id: id, name: container_name}, _extra_data) do defp get_value_for_key(:name, %{name: container_name} = assigns, _extra_data) do
assigns = %{id: id, container_name: container_name}
{container_name, {container_name,
~H""" ~H"""
<div class="flex flex-wrap justify-center items-center"> <div class="flex flex-wrap justify-center items-center">
<.link navigate={Routes.container_show_path(Endpoint, :show, @id)} class="link"> <.link navigate={~p"/container/#{@id}"} class="link">
<%= @container_name %> <%= @name %>
</.link> </.link>
</div> </div>
"""} """}

View File

@ -3,11 +3,11 @@ defmodule CanneryWeb.CoreComponents do
Provides core UI components. Provides core UI components.
""" """
use Phoenix.Component use Phoenix.Component
import CanneryWeb.{Gettext, ViewHelpers} use CanneryWeb, :verified_routes
import CanneryWeb.{Gettext, HTMLHelpers}
alias Cannery.{Accounts, Accounts.Invite, Accounts.User} alias Cannery.{Accounts, Accounts.Invite, Accounts.User}
alias Cannery.{Ammo, Ammo.Pack} alias Cannery.{Ammo, Ammo.Pack}
alias Cannery.{Containers.Container, Containers.Tag} alias Cannery.{Containers.Container, Containers.Tag}
alias CanneryWeb.{Endpoint, HomeLive}
alias CanneryWeb.Router.Helpers, as: Routes alias CanneryWeb.Router.Helpers, as: Routes
alias Phoenix.LiveView.{JS, Rendered} alias Phoenix.LiveView.{JS, Rendered}
@ -29,13 +29,13 @@ defmodule CanneryWeb.CoreComponents do
## Examples ## Examples
<.modal return_to={Routes.<%= schema.singular %>_index_path(Endpoint, :index)}> <.modal return_to={~p"/\#{<%= schema.plural %>}"}>
<.live_component <.live_component
module={<%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.FormComponent} module={<%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.FormComponent}
id={@<%= schema.singular %>.id || :new} id={@<%= schema.singular %>.id || :new}
title={@page_title} title={@page_title}
action={@live_action} action={@live_action}
return_to={Routes.<%= schema.singular %>_index_path(Endpoint, :index)} return_to={~p"/\#{<%= schema.singular %>}"}
<%= schema.singular %>: @<%= schema.singular %> <%= schema.singular %>: @<%= schema.singular %>
/> />
</.modal> </.modal>

View File

@ -5,7 +5,7 @@
border border-gray-400 rounded-lg shadow-lg hover:shadow-md border border-gray-400 rounded-lg shadow-lg hover:shadow-md
transition-all duration-300 ease-in-out" transition-all duration-300 ease-in-out"
> >
<.link navigate={Routes.container_show_path(Endpoint, :show, @container)} class="link"> <.link navigate={~p"/container/#{@container}"} class="link">
<h1 class="px-4 py-2 rounded-lg title text-xl"> <h1 class="px-4 py-2 rounded-lg title text-xl">
<%= @container.name %> <%= @container.name %>
</h1> </h1>

View File

@ -23,7 +23,7 @@
<% end %> <% end %>
<.qr_code <.qr_code
content={Routes.user_registration_url(Endpoint, :new, invite: @invite.token)} content={url(CanneryWeb.Endpoint, ~p"/users/register?invite=#{@invite.token}")}
filename={@invite.name} filename={@invite.name}
/> />
@ -36,7 +36,7 @@
id={"code-#{@invite.id}"} id={"code-#{@invite.id}"}
class="mx-2 my-1 text-xs px-4 py-2 rounded-lg text-center break-all text-gray-100 bg-primary-800" class="mx-2 my-1 text-xs px-4 py-2 rounded-lg text-center break-all text-gray-100 bg-primary-800"
phx-no-format phx-no-format
><%= Routes.user_registration_url(Endpoint, :new, invite: @invite.token) %></code> ><%= url(CanneryWeb.Endpoint, ~p"/users/register?invite=#{@invite.token}") %></code>
<%= if @code_actions, do: render_slot(@code_actions) %> <%= if @code_actions, do: render_slot(@code_actions) %>
</div> </div>

View File

@ -5,7 +5,7 @@
border border-gray-400 rounded-lg shadow-lg hover:shadow-md border border-gray-400 rounded-lg shadow-lg hover:shadow-md
transition-all duration-300 ease-in-out" transition-all duration-300 ease-in-out"
> >
<.link navigate={Routes.pack_show_path(Endpoint, :show, @pack)} class="mb-2 link"> <.link navigate={~p"/ammo/show/#{@pack}"} class="mb-2 link">
<h1 class="title text-xl title-primary-500"> <h1 class="title text-xl title-primary-500">
<%= @pack.type.name %> <%= @pack.type.name %>
</h1> </h1>
@ -55,7 +55,7 @@
<span :if={@container} class="rounded-lg title text-lg"> <span :if={@container} class="rounded-lg title text-lg">
<%= gettext("Container:") %> <%= gettext("Container:") %>
<.link navigate={Routes.container_show_path(Endpoint, :show, @container)} class="link"> <.link navigate={~p"/container/#{@container}"} class="link">
<%= @container.name %> <%= @container.name %>
</.link> </.link>
</span> </span>

View File

@ -1,12 +1,9 @@
<nav role="navigation" class="mb-8 px-8 py-4 w-full bg-primary-500"> <nav role="navigation" class="mb-8 px-8 py-4 w-full bg-primary-500">
<div class="flex flex-col sm:flex-row justify-between items-center"> <div class="flex flex-col sm:flex-row justify-between items-center">
<div class="mb-4 sm:mb-0 sm:mr-8 flex flex-row justify-start items-center space-x-2"> <div class="mb-4 sm:mb-0 sm:mr-8 flex flex-row justify-start items-center space-x-2">
<.link <.link navigate={~p"/"} class="inline mx-2 my-1 leading-5 text-xl text-white">
navigate={Routes.live_path(Endpoint, HomeLive)}
class="inline mx-2 my-1 leading-5 text-xl text-white"
>
<img <img
src={Routes.static_path(Endpoint, "/images/cannery.svg")} src={~p"/images/cannery.svg"}
alt={gettext("Cannery logo")} alt={gettext("Cannery logo")}
class="inline-block h-8 mx-1" class="inline-block h-8 mx-1"
/> />
@ -27,64 +24,43 @@
text-lg text-white text-ellipsis"> text-lg text-white text-ellipsis">
<%= if @current_user do %> <%= if @current_user do %>
<li class="mx-2 my-1"> <li class="mx-2 my-1">
<.link <.link navigate={~p"/tags"} class="text-white hover:underline">
navigate={Routes.tag_index_path(Endpoint, :index)}
class="text-white hover:underline"
>
<%= gettext("Tags") %> <%= gettext("Tags") %>
</.link> </.link>
</li> </li>
<li class="mx-2 my-1"> <li class="mx-2 my-1">
<.link <.link navigate={~p"/containers"} class="text-white hover:underline">
navigate={Routes.container_index_path(Endpoint, :index)}
class="text-white hover:underline"
>
<%= gettext("Containers") %> <%= gettext("Containers") %>
</.link> </.link>
</li> </li>
<li class="mx-2 my-1"> <li class="mx-2 my-1">
<.link <.link navigate={~p"/catalog"} class="text-white hover:underline">
navigate={Routes.type_index_path(Endpoint, :index)}
class="text-white hover:underline"
>
<%= gettext("Catalog") %> <%= gettext("Catalog") %>
</.link> </.link>
</li> </li>
<li class="mx-2 my-1"> <li class="mx-2 my-1">
<.link <.link navigate={~p"/ammo"} class="text-white hover:underline">
navigate={Routes.pack_index_path(Endpoint, :index)}
class="text-white hover:underline"
>
<%= gettext("Ammo") %> <%= gettext("Ammo") %>
</.link> </.link>
</li> </li>
<li class="mx-2 my-1"> <li class="mx-2 my-1">
<.link <.link navigate={~p"/range"} class="text-white hover:underline">
navigate={Routes.range_index_path(Endpoint, :index)}
class="text-white hover:underline"
>
<%= gettext("Range") %> <%= gettext("Range") %>
</.link> </.link>
</li> </li>
<li :if={@current_user |> Accounts.is_already_admin?()} class="mx-2 my-1"> <li :if={@current_user |> Accounts.is_already_admin?()} class="mx-2 my-1">
<.link <.link navigate={~p"/invites"} class="text-white hover:underline">
navigate={Routes.invite_index_path(Endpoint, :index)}
class="text-white hover:underline"
>
<%= gettext("Invites") %> <%= gettext("Invites") %>
</.link> </.link>
</li> </li>
<li class="mx-2 my-1"> <li class="mx-2 my-1">
<.link <.link href={~p"/users/settings"} class="text-white hover:underline truncate">
href={Routes.user_settings_path(Endpoint, :edit)}
class="text-white hover:underline truncate"
>
<%= @current_user.email %> <%= @current_user.email %>
</.link> </.link>
</li> </li>
<li class="mx-2 my-1"> <li class="mx-2 my-1">
<.link <.link
href={Routes.user_session_path(Endpoint, :delete)} href={~p"/users/log_out"}
method="delete" method="delete"
data-confirm={dgettext("prompts", "Are you sure you want to log out?")} data-confirm={dgettext("prompts", "Are you sure you want to log out?")}
aria-label={gettext("Log out")} aria-label={gettext("Log out")}
@ -100,7 +76,7 @@
class="mx-2 my-1" class="mx-2 my-1"
> >
<.link <.link
navigate={Routes.live_dashboard_path(Endpoint, :home)} navigate={~p"/dashboard"}
class="text-white hover:underline" class="text-white hover:underline"
aria-label={gettext("Live Dashboard")} aria-label={gettext("Live Dashboard")}
> >
@ -109,18 +85,12 @@
</li> </li>
<% else %> <% else %>
<li :if={Accounts.allow_registration?()} class="mx-2 my-1"> <li :if={Accounts.allow_registration?()} class="mx-2 my-1">
<.link <.link href={~p"/users/register"} class="text-white hover:underline truncate">
href={Routes.user_registration_path(Endpoint, :new)}
class="text-white hover:underline truncate"
>
<%= dgettext("actions", "Register") %> <%= dgettext("actions", "Register") %>
</.link> </.link>
</li> </li>
<li class="mx-2 my-1"> <li class="mx-2 my-1">
<.link <.link href={~p"/users/log_in"} class="text-white hover:underline truncate">
href={Routes.user_session_path(Endpoint, :new)}
class="text-white hover:underline truncate"
>
<%= dgettext("actions", "Log in") %> <%= dgettext("actions", "Log in") %>
</.link> </.link>
</li> </li>

View File

@ -0,0 +1,10 @@
defmodule CanneryWeb.EmailHTML do
@moduledoc """
Renders email templates
"""
use CanneryWeb, :html
embed_templates "email_html/*.html", suffix: "_html"
embed_templates "email_html/*.txt", suffix: "_text"
end

View File

@ -9,4 +9,4 @@
<%= dgettext("emails", <%= dgettext("emails",
"If you didn't create an account at %{url}, please ignore this.", "If you didn't create an account at %{url}, please ignore this.",
url: Routes.live_url(Endpoint, HomeLive)) %> url: ~p"/") %>

View File

@ -7,4 +7,4 @@
<%= dgettext("emails", <%= dgettext("emails",
"If you didn't request this change from %{url}, please ignore this.", "If you didn't request this change from %{url}, please ignore this.",
url: Routes.live_url(Endpoint, HomeLive)) %> url: ~p"/") %>

View File

@ -7,4 +7,4 @@
<%= dgettext("emails", <%= dgettext("emails",
"If you didn't request this change from %{url}, please ignore this.", "If you didn't request this change from %{url}, please ignore this.",
url: Routes.live_url(Endpoint, HomeLive)) %> url: ~p"/") %>

View File

@ -0,0 +1,17 @@
defmodule CanneryWeb.Layouts do
@moduledoc """
The root layouts for the entire application
"""
use CanneryWeb, :html
embed_templates "layouts/*"
def get_title(%{assigns: %{title: title}}) when title not in [nil, ""] do
gettext("Cannery | %{title}", title: title)
end
def get_title(_conn) do
gettext("Cannery")
end
end

View File

@ -3,11 +3,11 @@
<.topbar current_user={assigns[:current_user]} /> <.topbar current_user={assigns[:current_user]} />
<div class="mx-8 my-2 flex flex-col space-y-4 text-center"> <div class="mx-8 my-2 flex flex-col space-y-4 text-center">
<p :if={get_flash(@conn, :info)} class="alert alert-info" role="alert"> <p :if={@flash["info"]} class="alert alert-info" role="alert">
<%= get_flash(@conn, :info) %> <%= @flash["info"] %>
</p> </p>
<p :if={get_flash(@conn, :error)} class="alert alert-danger" role="alert"> <p :if={@flash["error"]} class="alert alert-danger" role="alert">
<%= get_flash(@conn, :error) %> <%= @flash["error"] %>
</p> </p>
</div> </div>
</header> </header>

View File

@ -9,7 +9,7 @@
<hr style="margin: 2em auto; border-width: 1px; border-color: rgb(212, 212, 216); width: 100%; max-width: 42rem;" /> <hr style="margin: 2em auto; border-width: 1px; border-color: rgb(212, 212, 216); width: 100%; max-width: 42rem;" />
<a style="color: rgb(31, 31, 31);" href={Routes.live_url(Endpoint, HomeLive)}> <a style="color: rgb(31, 31, 31);" href={~p"/"}>
<%= dgettext( <%= dgettext(
"emails", "emails",
"This email was sent from Cannery, the self-hosted firearm tracker website." "This email was sent from Cannery, the self-hosted firearm tracker website."

View File

@ -8,4 +8,4 @@
<%= dgettext("emails", <%= dgettext("emails",
"This email was sent from Cannery at %{url}, the self-hosted firearm tracker website.", "This email was sent from Cannery at %{url}, the self-hosted firearm tracker website.",
url: Routes.live_url(Endpoint, HomeLive)) %> url: ~p"/") %>

View File

@ -5,21 +5,12 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<%= csrf_meta_tag() %> <%= csrf_meta_tag() %>
<link <link rel="shortcut icon" type="image/jpg" href={~p"/images/cannery.svg"} />
rel="shortcut icon"
type="image/jpg"
href={Routes.static_path(@conn, "/images/cannery.svg")}
/>
<.live_title suffix={" | #{gettext("Cannery")}"}> <.live_title suffix={" | #{gettext("Cannery")}"}>
<%= assigns[:page_title] || gettext("Cannery") %> <%= assigns[:page_title] || gettext("Cannery") %>
</.live_title> </.live_title>
<link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/css/app.css")} /> <link phx-track-static rel="stylesheet" href={~p"/css/app.css"} />
<script <script defer phx-track-static type="text/javascript" src={~p"/js/app.js"}>
defer
phx-track-static
type="text/javascript"
src={Routes.static_path(@conn, "/js/app.js")}
>
</script> </script>
</head> </head>

View File

@ -5,7 +5,6 @@ defmodule CanneryWeb.Components.MovePackComponent do
use CanneryWeb, :live_component use CanneryWeb, :live_component
alias Cannery.{Accounts.User, Ammo, Ammo.Pack, Containers, Containers.Container} alias Cannery.{Accounts.User, Ammo, Ammo.Pack, Containers, Containers.Container}
alias CanneryWeb.Endpoint
alias Ecto.Changeset alias Ecto.Changeset
alias Phoenix.LiveView.Socket alias Phoenix.LiveView.Socket
@ -84,7 +83,7 @@ defmodule CanneryWeb.Components.MovePackComponent do
<%= display_emoji("😔") %> <%= display_emoji("😔") %>
</h2> </h2>
<.link navigate={Routes.container_index_path(Endpoint, :new)} class="btn btn-primary"> <.link navigate={~p"/containers/new"} class="btn btn-primary">
<%= dgettext("actions", "Add another container!") %> <%= dgettext("actions", "Add another container!") %>
</.link> </.link>
<% else %> <% else %>

View File

@ -98,7 +98,7 @@ defmodule CanneryWeb.Components.ShotRecordTableComponent do
{pack.type.name, {pack.type.name,
~H""" ~H"""
<.link navigate={Routes.pack_show_path(Endpoint, :show, @pack)} class="link"> <.link navigate={~p"/ammo/show/#{@pack}"} class="link">
<%= @pack.type.name %> <%= @pack.type.name %>
</.link> </.link>
"""} """}

View File

@ -261,13 +261,11 @@ defmodule CanneryWeb.Components.TypeTableComponent do
end end
end end
defp get_type_value(:name, _key, %{name: type_name} = type, _other_data) do defp get_type_value(:name, _key, %{name: type_name} = assigns, _other_data) do
assigns = %{type: type}
{type_name, {type_name,
~H""" ~H"""
<.link navigate={Routes.type_show_path(Endpoint, :show, @type)} class="link"> <.link navigate={~p"/type/#{@id}"} class="link">
<%= @type.name %> <%= @name %>
</.link> </.link>
"""} """}
end end

View File

@ -6,7 +6,8 @@ defmodule CanneryWeb.EmailController do
use CanneryWeb, :controller use CanneryWeb, :controller
alias Cannery.Accounts.User alias Cannery.Accounts.User
plug :put_layout, {CanneryWeb.LayoutView, :email} plug :put_root_layout, html: {CanneryWeb.Layouts, :email_html}
plug :put_layout, false
@sample_assigns %{ @sample_assigns %{
email: %{subject: "Example subject"}, email: %{subject: "Example subject"},
@ -18,6 +19,6 @@ defmodule CanneryWeb.EmailController do
Debug route used to preview emails Debug route used to preview emails
""" """
def preview(conn, %{"id" => template}) do def preview(conn, %{"id" => template}) do
render(conn, "#{template |> to_string()}.html", @sample_assigns) render(conn, String.to_existing_atom(template), @sample_assigns)
end end
end end

View File

@ -0,0 +1,16 @@
defmodule CanneryWeb.ErrorHTML do
use CanneryWeb, :html
embed_templates "error_html/*"
def render(template, _assigns) do
error_string =
case template do
"404.html" -> dgettext("errors", "Not found")
"401.html" -> dgettext("errors", "Unauthorized")
_other_path -> dgettext("errors", "Internal server error")
end
error(%{error_string: error_string})
end
end

View File

@ -24,10 +24,7 @@
<hr class="w-full hr" /> <hr class="w-full hr" />
<.link <.link href={~p"/"} class="link title text-primary-600 text-lg">
href={Routes.live_path(Endpoint, HomeLive)}
class="link title text-primary-600 text-lg"
>
<%= dgettext("errors", "Go back home") %> <%= dgettext("errors", "Go back home") %>
</.link> </.link>
</div> </div>

View File

@ -0,0 +1,14 @@
defmodule CanneryWeb.ErrorJSON do
import CanneryWeb.Gettext
def render(template, _assigns) do
error_string =
case template do
"404.json" -> dgettext("errors", "Not found")
"401.json" -> dgettext("errors", "Unauthorized")
_other_path -> dgettext("errors", "Internal server error")
end
%{errors: %{detail: error_string}}
end
end

View File

@ -1,11 +0,0 @@
defmodule CanneryWeb.HomeController do
@moduledoc """
Controller for home page
"""
use CanneryWeb, :controller
def index(conn, _params) do
render(conn, "index.html")
end
end

View File

@ -0,0 +1,5 @@
defmodule CanneryWeb.HomeHTML do
use CanneryWeb, :html
embed_templates "home_html/*"
end

View File

@ -3,12 +3,11 @@ defmodule CanneryWeb.UserAuth do
Functions for user session and authentication Functions for user session and authentication
""" """
use CanneryWeb, :verified_routes
import Plug.Conn import Plug.Conn
import Phoenix.Controller import Phoenix.Controller
import CanneryWeb.Gettext import CanneryWeb.Gettext
alias Cannery.{Accounts, Accounts.User} alias Cannery.{Accounts, Accounts.User}
alias CanneryWeb.HomeLive
alias CanneryWeb.Router.Helpers, as: Routes
# Make the remember me cookie valid for 60 days. # Make the remember me cookie valid for 60 days.
# If you want bump or reduce this value, also change # If you want bump or reduce this value, also change
@ -39,7 +38,7 @@ defmodule CanneryWeb.UserAuth do
dgettext("errors", "You must confirm your account and log in to access this page.") dgettext("errors", "You must confirm your account and log in to access this page.")
) )
|> maybe_store_return_to() |> maybe_store_return_to()
|> redirect(to: Routes.user_session_path(conn, :new)) |> redirect(to: ~p"/users/log_in")
|> halt() |> halt()
end end
@ -49,8 +48,7 @@ defmodule CanneryWeb.UserAuth do
conn conn
|> renew_session() |> renew_session()
|> put_session(:user_token, token) |> put_token_in_session(token)
|> put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}")
|> maybe_write_remember_me_cookie(token, params) |> maybe_write_remember_me_cookie(token, params)
|> redirect(to: user_return_to || signed_in_path(conn)) |> redirect(to: user_return_to || signed_in_path(conn))
end end
@ -96,7 +94,7 @@ defmodule CanneryWeb.UserAuth do
""" """
def log_out_user(conn) do def log_out_user(conn) do
user_token = get_session(conn, :user_token) user_token = get_session(conn, :user_token)
user_token && Accounts.delete_session_token(user_token) user_token && Accounts.delete_user_session_token(user_token)
if live_socket_id = get_session(conn, :live_socket_id) do if live_socket_id = get_session(conn, :live_socket_id) do
CanneryWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{}) CanneryWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{})
@ -105,7 +103,7 @@ defmodule CanneryWeb.UserAuth do
conn conn
|> renew_session() |> renew_session()
|> delete_resp_cookie(@remember_me_cookie) |> delete_resp_cookie(@remember_me_cookie)
|> redirect(to: "/") |> redirect(to: ~p"/")
end end
@doc """ @doc """
@ -119,19 +117,110 @@ defmodule CanneryWeb.UserAuth do
end end
defp ensure_user_token(conn) do defp ensure_user_token(conn) do
if user_token = get_session(conn, :user_token) do if token = get_session(conn, :user_token) do
{user_token, conn} {token, conn}
else else
conn = fetch_cookies(conn, signed: [@remember_me_cookie]) conn = fetch_cookies(conn, signed: [@remember_me_cookie])
if user_token = conn.cookies[@remember_me_cookie] do if token = conn.cookies[@remember_me_cookie] do
{user_token, put_session(conn, :user_token, user_token)} {token, put_token_in_session(conn, token)}
else else
{nil, conn} {nil, conn}
end end
end end
end end
@doc """
Handles mounting and authenticating the current_user in LiveViews.
## `on_mount` arguments
* `:mount_current_user` - Assigns current_user
to socket assigns based on user_token, or nil if
there's no user_token or no matching user.
* `:ensure_authenticated` - Authenticates the user from the session,
and assigns the current_user to socket assigns based
on user_token.
Redirects to login page if there's no logged user.
* `:redirect_if_user_is_authenticated` - Authenticates the user from the session.
Redirects to signed_in_path if there's a logged user.
## Examples
Use the `on_mount` lifecycle macro in LiveViews to mount or authenticate
the current_user:
defmodule CanneryWeb.PageLive do
use CanneryWeb, :live_view
on_mount {CanneryWeb.UserAuth, :mount_current_user}
...
end
Or use the `live_session` of your router to invoke the on_mount callback:
live_session :authenticated, on_mount: [{CanneryWeb.UserAuth, :ensure_authenticated}] do
live "/profile", ProfileLive, :index
end
"""
def on_mount(:mount_current_user, _params, session, socket) do
{:cont, mount_current_user(session, socket)}
end
def on_mount(:ensure_authenticated, _params, session, socket) do
socket = mount_current_user(session, socket)
if socket.assigns.current_user do
{:cont, socket}
else
error_flash = dgettext("errors", "You must log in to access this page.")
socket =
socket
|> Phoenix.LiveView.put_flash(:error, error_flash)
|> Phoenix.LiveView.redirect(to: ~p"/users/log_in")
{:halt, socket}
end
end
def on_mount(:ensure_admin, _params, session, socket) do
socket = mount_current_user(session, socket)
if socket.assigns.current_user && socket.assigns.current_user.role == :admin do
{:cont, socket}
else
error_flash = dgettext("errors", "You must log in as an administrator to access this page.")
socket =
socket
|> Phoenix.LiveView.put_flash(:error, error_flash)
|> Phoenix.LiveView.redirect(to: ~p"/users/log_in")
{:halt, socket}
end
end
def on_mount(:redirect_if_user_is_authenticated, _params, session, socket) do
socket = mount_current_user(session, socket)
if socket.assigns.current_user do
{:halt, Phoenix.LiveView.redirect(socket, to: signed_in_path(socket))}
else
{:cont, socket}
end
end
defp mount_current_user(session, socket) do
Phoenix.Component.assign_new(socket, :current_user, fn ->
if user_token = session["user_token"] do
Accounts.get_user_by_session_token(user_token)
end
end)
end
@doc """ @doc """
Used for routes that require the user to not be authenticated. Used for routes that require the user to not be authenticated.
""" """
@ -161,7 +250,7 @@ defmodule CanneryWeb.UserAuth do
dgettext("errors", "You must confirm your account and log in to access this page.") dgettext("errors", "You must confirm your account and log in to access this page.")
) )
|> maybe_store_return_to() |> maybe_store_return_to()
|> redirect(to: Routes.user_session_path(conn, :new)) |> redirect(to: ~p"/users/log_in")
|> halt() |> halt()
end end
end end
@ -176,16 +265,34 @@ defmodule CanneryWeb.UserAuth do
conn conn
|> put_flash(:error, dgettext("errors", "You are not authorized to view this page.")) |> put_flash(:error, dgettext("errors", "You are not authorized to view this page."))
|> maybe_store_return_to() |> maybe_store_return_to()
|> redirect(to: Routes.live_path(conn, HomeLive)) |> redirect(to: ~p"/")
|> halt() |> halt()
end end
end end
def put_user_locale(%{assigns: %{current_user: %{locale: locale}}} = conn, _opts) do
default = Application.fetch_env!(:gettext, :default_locale)
Gettext.put_locale(locale || default)
conn |> put_session(:locale, locale || default)
end
def put_user_locale(conn, _opts) do
default = Application.fetch_env!(:gettext, :default_locale)
Gettext.put_locale(default)
conn |> put_session(:locale, default)
end
defp put_token_in_session(conn, token) do
conn
|> put_session(:user_token, token)
|> put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}")
end
defp maybe_store_return_to(%{method: "GET"} = conn) do defp maybe_store_return_to(%{method: "GET"} = conn) do
put_session(conn, :user_return_to, current_path(conn)) put_session(conn, :user_return_to, current_path(conn))
end end
defp maybe_store_return_to(conn), do: conn defp maybe_store_return_to(conn), do: conn
defp signed_in_path(_conn), do: "/" defp signed_in_path(_conn), do: ~p"/"
end end

View File

@ -5,14 +5,14 @@ defmodule CanneryWeb.UserConfirmationController do
alias Cannery.Accounts alias Cannery.Accounts
def new(conn, _params) do def new(conn, _params) do
render(conn, "new.html", page_title: gettext("Confirm your account")) render(conn, :new, page_title: gettext("Confirm your account"))
end end
def create(conn, %{"user" => %{"email" => email}}) do def create(conn, %{"user" => %{"email" => email}}) do
if user = Accounts.get_user_by_email(email) do if user = Accounts.get_user_by_email(email) do
Accounts.deliver_user_confirmation_instructions( Accounts.deliver_user_confirmation_instructions(
user, user,
&Routes.user_confirmation_url(conn, :confirm, &1) fn token -> url(CanneryWeb.Endpoint, ~p"/users/confirm/#{token}") end
) )
end end
@ -22,11 +22,10 @@ defmodule CanneryWeb.UserConfirmationController do
:info, :info,
dgettext( dgettext(
"prompts", "prompts",
"If your email is in our system and it has not been confirmed yet, " <> "If your email is in our system and it has not been confirmed yet, you will receive an email with instructions shortly."
"you will receive an email with instructions shortly."
) )
) )
|> redirect(to: "/") |> redirect(to: ~p"/")
end end
# Do not log in the user after confirmation to avoid a # Do not log in the user after confirmation to avoid a
@ -36,7 +35,7 @@ defmodule CanneryWeb.UserConfirmationController do
{:ok, %{email: email}} -> {:ok, %{email: email}} ->
conn conn
|> put_flash(:info, dgettext("prompts", "%{email} confirmed successfully.", email: email)) |> put_flash(:info, dgettext("prompts", "%{email} confirmed successfully.", email: email))
|> redirect(to: "/") |> redirect(to: ~p"/")
:error -> :error ->
# If there is a current user and the account was already confirmed, # If there is a current user and the account was already confirmed,
@ -45,7 +44,7 @@ defmodule CanneryWeb.UserConfirmationController do
# a warning message. # a warning message.
case conn.assigns do case conn.assigns do
%{current_user: %{confirmed_at: confirmed_at}} when not is_nil(confirmed_at) -> %{current_user: %{confirmed_at: confirmed_at}} when not is_nil(confirmed_at) ->
redirect(conn, to: "/") redirect(conn, to: ~p"/")
%{} -> %{} ->
conn conn
@ -53,7 +52,7 @@ defmodule CanneryWeb.UserConfirmationController do
:error, :error,
dgettext("errors", "User confirmation link is invalid or it has expired.") dgettext("errors", "User confirmation link is invalid or it has expired.")
) )
|> redirect(to: "/") |> redirect(to: ~p"/")
end end
end end
end end

View File

@ -0,0 +1,6 @@
defmodule CanneryWeb.UserConfirmationHTML do
use CanneryWeb, :html
alias Cannery.Accounts
embed_templates "user_confirmation_html/*"
end

View File

@ -7,7 +7,7 @@
:let={f} :let={f}
for={%{}} for={%{}}
as={:user} as={:user}
action={Routes.user_confirmation_path(@conn, :create)} action={~p"/users/confirm"}
class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center" class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center"
> >
<%= label(f, :email, gettext("Email"), class: "title text-lg text-primary-600") %> <%= label(f, :email, gettext("Email"), class: "title text-lg text-primary-600") %>
@ -21,14 +21,10 @@
<hr class="hr" /> <hr class="hr" />
<div class="flex flex-row justify-center items-center space-x-4"> <div class="flex flex-row justify-center items-center space-x-4">
<.link <.link :if={Accounts.allow_registration?()} href={~p"/users/register"} class="btn btn-primary">
:if={Accounts.allow_registration?()}
href={Routes.user_registration_path(@conn, :new)}
class="btn btn-primary"
>
<%= dgettext("actions", "Register") %> <%= dgettext("actions", "Register") %>
</.link> </.link>
<.link href={Routes.user_session_path(@conn, :new)} class="btn btn-primary"> <.link href={~p"/users/log_in"} class="btn btn-primary">
<%= dgettext("actions", "Log in") %> <%= dgettext("actions", "Log in") %>
</.link> </.link>
</div> </div>

View File

@ -2,7 +2,6 @@ defmodule CanneryWeb.UserRegistrationController do
use CanneryWeb, :controller use CanneryWeb, :controller
import CanneryWeb.Gettext import CanneryWeb.Gettext
alias Cannery.{Accounts, Accounts.Invites} alias Cannery.{Accounts, Accounts.Invites}
alias CanneryWeb.{Endpoint, HomeLive}
alias Ecto.Changeset alias Ecto.Changeset
def new(conn, %{"invite" => invite_token}) do def new(conn, %{"invite" => invite_token}) do
@ -11,7 +10,7 @@ defmodule CanneryWeb.UserRegistrationController do
else else
conn conn
|> put_flash(:error, dgettext("errors", "Sorry, this invite was not found or expired")) |> put_flash(:error, dgettext("errors", "Sorry, this invite was not found or expired"))
|> redirect(to: Routes.live_path(Endpoint, HomeLive)) |> redirect(to: ~p"/")
end end
end end
@ -21,13 +20,13 @@ defmodule CanneryWeb.UserRegistrationController do
else else
conn conn
|> put_flash(:error, dgettext("errors", "Sorry, public registration is disabled")) |> put_flash(:error, dgettext("errors", "Sorry, public registration is disabled"))
|> redirect(to: Routes.live_path(Endpoint, HomeLive)) |> redirect(to: ~p"/")
end end
end end
# renders new user registration page # renders new user registration page
defp render_new(conn, invite_token \\ nil) do defp render_new(conn, invite_token \\ nil) do
render(conn, "new.html", render(conn, :new,
changeset: Accounts.change_user_registration(), changeset: Accounts.change_user_registration(),
invite_token: invite_token, invite_token: invite_token,
page_title: gettext("Register") page_title: gettext("Register")
@ -40,7 +39,7 @@ defmodule CanneryWeb.UserRegistrationController do
else else
conn conn
|> put_flash(:error, dgettext("errors", "Sorry, this invite was not found or expired")) |> put_flash(:error, dgettext("errors", "Sorry, this invite was not found or expired"))
|> redirect(to: Routes.live_path(Endpoint, HomeLive)) |> redirect(to: ~p"/")
end end
end end
@ -50,7 +49,7 @@ defmodule CanneryWeb.UserRegistrationController do
else else
conn conn
|> put_flash(:error, dgettext("errors", "Sorry, public registration is disabled")) |> put_flash(:error, dgettext("errors", "Sorry, public registration is disabled"))
|> redirect(to: Routes.live_path(Endpoint, HomeLive)) |> redirect(to: ~p"/")
end end
end end
@ -59,17 +58,17 @@ defmodule CanneryWeb.UserRegistrationController do
{:ok, user} -> {:ok, user} ->
Accounts.deliver_user_confirmation_instructions( Accounts.deliver_user_confirmation_instructions(
user, user,
&Routes.user_confirmation_url(conn, :confirm, &1) fn token -> url(CanneryWeb.Endpoint, ~p"/users/confirm/#{token}") end
) )
conn conn
|> put_flash(:info, dgettext("prompts", "Please check your email to verify your account")) |> put_flash(:info, dgettext("prompts", "Please check your email to verify your account"))
|> redirect(to: Routes.user_session_path(Endpoint, :new)) |> redirect(to: ~p"/users/log_in")
{:error, :invalid_token} -> {:error, :invalid_token} ->
conn conn
|> put_flash(:error, dgettext("errors", "Sorry, this invite was not found or expired")) |> put_flash(:error, dgettext("errors", "Sorry, this invite was not found or expired"))
|> redirect(to: Routes.live_path(Endpoint, HomeLive)) |> redirect(to: ~p"/")
{:error, %Changeset{} = changeset} -> {:error, %Changeset{} = changeset} ->
conn |> render("new.html", changeset: changeset, invite_token: invite_token) conn |> render("new.html", changeset: changeset, invite_token: invite_token)

View File

@ -0,0 +1,5 @@
defmodule CanneryWeb.UserRegistrationHTML do
use CanneryWeb, :html
embed_templates "user_registration_html/*"
end

View File

@ -6,7 +6,7 @@
<.form <.form
:let={f} :let={f}
for={@changeset} for={@changeset}
action={Routes.user_registration_path(@conn, :create)} action={~p"/users/register"}
class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center" class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center"
> >
<p :if={@changeset.action && not @changeset.valid?()} class="alert alert-danger col-span-3"> <p :if={@changeset.action && not @changeset.valid?()} class="alert alert-danger col-span-3">
@ -40,10 +40,10 @@
<hr class="hr" /> <hr class="hr" />
<div class="flex flex-row justify-center items-center space-x-4"> <div class="flex flex-row justify-center items-center space-x-4">
<.link href={Routes.user_session_path(@conn, :new)} class="btn btn-primary"> <.link href={~p"/users/log_in"} class="btn btn-primary">
<%= dgettext("actions", "Log in") %> <%= dgettext("actions", "Log in") %>
</.link> </.link>
<.link href={Routes.user_reset_password_path(@conn, :new)} class="btn btn-primary"> <.link href={~p"/users/reset_password"} class="btn btn-primary">
<%= dgettext("actions", "Forgot your password?") %> <%= dgettext("actions", "Forgot your password?") %>
</.link> </.link>
</div> </div>

View File

@ -6,14 +6,14 @@ defmodule CanneryWeb.UserResetPasswordController do
plug :get_user_by_reset_password_token when action in [:edit, :update] plug :get_user_by_reset_password_token when action in [:edit, :update]
def new(conn, _params) do def new(conn, _params) do
render(conn, "new.html", page_title: gettext("Forgot your password?")) render(conn, :new, page_title: gettext("Forgot your password?"))
end end
def create(conn, %{"user" => %{"email" => email}}) do def create(conn, %{"user" => %{"email" => email}}) do
if user = Accounts.get_user_by_email(email) do if user = Accounts.get_user_by_email(email) do
Accounts.deliver_user_reset_password_instructions( Accounts.deliver_user_reset_password_instructions(
user, user,
&Routes.user_reset_password_url(conn, :edit, &1) fn token -> url(CanneryWeb.Endpoint, ~p"/users/reset_password/#{token}") end
) )
end end
@ -23,15 +23,14 @@ defmodule CanneryWeb.UserResetPasswordController do
:info, :info,
dgettext( dgettext(
"prompts", "prompts",
"If your email is in our system, you will receive instructions to " <> "If your email is in our system, you will receive instructions to reset your password shortly."
"reset your password shortly."
) )
) )
|> redirect(to: "/") |> redirect(to: ~p"/")
end end
def edit(conn, _params) do def edit(conn, _params) do
render(conn, "edit.html", render(conn, :edit,
changeset: Accounts.change_user_password(conn.assigns.user), changeset: Accounts.change_user_password(conn.assigns.user),
page_title: gettext("Reset your password") page_title: gettext("Reset your password")
) )
@ -44,10 +43,10 @@ defmodule CanneryWeb.UserResetPasswordController do
{:ok, _} -> {:ok, _} ->
conn conn
|> put_flash(:info, dgettext("prompts", "Password reset successfully.")) |> put_flash(:info, dgettext("prompts", "Password reset successfully."))
|> redirect(to: Routes.user_session_path(conn, :new)) |> redirect(to: ~p"/users/log_in")
{:error, changeset} -> {:error, changeset} ->
render(conn, "edit.html", changeset: changeset) render(conn, :edit, changeset: changeset)
end end
end end
@ -62,7 +61,7 @@ defmodule CanneryWeb.UserResetPasswordController do
:error, :error,
dgettext("errors", "Reset password link is invalid or it has expired.") dgettext("errors", "Reset password link is invalid or it has expired.")
) )
|> redirect(to: "/") |> redirect(to: ~p"/")
|> halt() |> halt()
end end
end end

View File

@ -0,0 +1,6 @@
defmodule CanneryWeb.UserResetPasswordHTML do
use CanneryWeb, :html
alias Cannery.Accounts
embed_templates "user_reset_password_html/*"
end

View File

@ -6,7 +6,7 @@
<.form <.form
:let={f} :let={f}
for={@changeset} for={@changeset}
action={Routes.user_reset_password_path(@conn, :update, @token)} action={~p"/users/reset_password/#{@token}"}
class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center" class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center"
> >
<p :if={@changeset.action && not @changeset.valid?()} class="alert alert-danger col-span-3"> <p :if={@changeset.action && not @changeset.valid?()} class="alert alert-danger col-span-3">
@ -34,14 +34,10 @@
<hr class="hr" /> <hr class="hr" />
<div class="flex flex-row justify-center items-center space-x-4"> <div class="flex flex-row justify-center items-center space-x-4">
<.link <.link :if={Accounts.allow_registration?()} href={~p"/users/register"} class="btn btn-primary">
:if={Accounts.allow_registration?()}
href={Routes.user_registration_path(@conn, :new)}
class="btn btn-primary"
>
<%= dgettext("actions", "Register") %> <%= dgettext("actions", "Register") %>
</.link> </.link>
<.link href={Routes.user_session_path(@conn, :new)} class="btn btn-primary"> <.link href={~p"/users/log_in"} class="btn btn-primary">
<%= dgettext("actions", "Log in") %> <%= dgettext("actions", "Log in") %>
</.link> </.link>
</div> </div>

View File

@ -7,7 +7,7 @@
:let={f} :let={f}
for={%{}} for={%{}}
as={:user} as={:user}
action={Routes.user_reset_password_path(@conn, :create)} action={~p"/users/reset_password"}
class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center" class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center"
> >
<%= label(f, :email, gettext("Email"), class: "title text-lg text-primary-600") %> <%= label(f, :email, gettext("Email"), class: "title text-lg text-primary-600") %>
@ -21,14 +21,10 @@
<hr class="hr" /> <hr class="hr" />
<div class="flex flex-row justify-center items-center space-x-4"> <div class="flex flex-row justify-center items-center space-x-4">
<.link <.link :if={Accounts.allow_registration?()} href={~p"/users/register"} class="btn btn-primary">
:if={Accounts.allow_registration?()}
href={Routes.user_registration_path(@conn, :new)}
class="btn btn-primary"
>
<%= dgettext("actions", "Register") %> <%= dgettext("actions", "Register") %>
</.link> </.link>
<.link href={Routes.user_session_path(@conn, :new)} class="btn btn-primary"> <.link href={~p"/users/log_in"} class="btn btn-primary">
<%= dgettext("actions", "Log in") %> <%= dgettext("actions", "Log in") %>
</.link> </.link>
</div> </div>

View File

@ -5,7 +5,7 @@ defmodule CanneryWeb.UserSessionController do
alias CanneryWeb.UserAuth alias CanneryWeb.UserAuth
def new(conn, _params) do def new(conn, _params) do
render(conn, "new.html", error_message: nil, page_title: gettext("Log in")) render(conn, :new, error_message: nil, page_title: gettext("Log in"))
end end
def create(conn, %{"user" => user_params}) do def create(conn, %{"user" => user_params}) do
@ -14,7 +14,7 @@ defmodule CanneryWeb.UserSessionController do
if user = Accounts.get_user_by_email_and_password(email, password) do if user = Accounts.get_user_by_email_and_password(email, password) do
UserAuth.log_in_user(conn, user, user_params) UserAuth.log_in_user(conn, user, user_params)
else else
render(conn, "new.html", error_message: dgettext("errors", "Invalid email or password")) render(conn, :new, error_message: dgettext("errors", "Invalid email or password"))
end end
end end

View File

@ -0,0 +1,6 @@
defmodule CanneryWeb.UserSessionHTML do
use CanneryWeb, :html
alias Cannery.Accounts
embed_templates "user_session_html/*"
end

View File

@ -6,8 +6,8 @@
<.form <.form
:let={f} :let={f}
for={@conn} for={@conn}
action={Routes.user_session_path(@conn, :create)} action={~p"/users/log_in"}
as="user" as={:user}
class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center" class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center"
> >
<p :if={@error_message} class="alert alert-danger col-span-3"> <p :if={@error_message} class="alert alert-danger col-span-3">
@ -31,14 +31,10 @@
<hr class="hr" /> <hr class="hr" />
<div class="flex flex-row justify-center items-center space-x-4"> <div class="flex flex-row justify-center items-center space-x-4">
<.link <.link :if={Accounts.allow_registration?()} href={~p"/users/register"} class="btn btn-primary">
:if={Accounts.allow_registration?()}
href={Routes.user_registration_path(@conn, :new)}
class="btn btn-primary"
>
<%= dgettext("actions", "Register") %> <%= dgettext("actions", "Register") %>
</.link> </.link>
<.link href={Routes.user_reset_password_path(@conn, :new)} class="btn btn-primary"> <.link href={~p"/users/reset_password"} class="btn btn-primary">
<%= dgettext("actions", "Forgot your password?") %> <%= dgettext("actions", "Forgot your password?") %>
</.link> </.link>
</div> </div>

View File

@ -2,12 +2,12 @@ defmodule CanneryWeb.UserSettingsController do
use CanneryWeb, :controller use CanneryWeb, :controller
import CanneryWeb.Gettext import CanneryWeb.Gettext
alias Cannery.Accounts alias Cannery.Accounts
alias CanneryWeb.{HomeLive, UserAuth} alias CanneryWeb.UserAuth
plug :assign_email_and_password_changesets plug :assign_email_and_password_changesets
def edit(conn, _params) do def edit(conn, _params) do
render(conn, "edit.html", page_title: gettext("Settings")) render(conn, :edit, page_title: gettext("Settings"))
end end
def update(%{assigns: %{current_user: user}} = conn, %{ def update(%{assigns: %{current_user: user}} = conn, %{
@ -20,7 +20,7 @@ defmodule CanneryWeb.UserSettingsController do
Accounts.deliver_update_email_instructions( Accounts.deliver_update_email_instructions(
applied_user, applied_user,
user.email, user.email,
&Routes.user_settings_url(conn, :confirm_email, &1) fn token -> url(CanneryWeb.Endpoint, ~p"/users/settings/confirm_email/#{token}") end
) )
conn conn
@ -31,10 +31,10 @@ defmodule CanneryWeb.UserSettingsController do
"A link to confirm your email change has been sent to the new address." "A link to confirm your email change has been sent to the new address."
) )
) )
|> redirect(to: Routes.user_settings_path(conn, :edit)) |> redirect(to: ~p"/users/settings")
{:error, changeset} -> {:error, changeset} ->
conn |> render("edit.html", email_changeset: changeset) conn |> render(:edit, email_changeset: changeset)
end end
end end
@ -47,11 +47,11 @@ defmodule CanneryWeb.UserSettingsController do
{:ok, user} -> {:ok, user} ->
conn conn
|> put_flash(:info, dgettext("prompts", "Password updated successfully.")) |> put_flash(:info, dgettext("prompts", "Password updated successfully."))
|> put_session(:user_return_to, Routes.user_settings_path(conn, :edit)) |> put_session(:user_return_to, ~p"/users/settings")
|> UserAuth.log_in_user(user) |> UserAuth.log_in_user(user)
{:error, changeset} -> {:error, changeset} ->
conn |> render("edit.html", password_changeset: changeset) conn |> render(:edit, password_changeset: changeset)
end end
end end
@ -63,10 +63,10 @@ defmodule CanneryWeb.UserSettingsController do
{:ok, _user} -> {:ok, _user} ->
conn conn
|> put_flash(:info, dgettext("prompts", "Language updated successfully.")) |> put_flash(:info, dgettext("prompts", "Language updated successfully."))
|> redirect(to: Routes.user_settings_path(conn, :edit)) |> redirect(to: ~p"/users/settings")
{:error, changeset} -> {:error, changeset} ->
conn |> render("edit.html", locale_changeset: changeset) conn |> render(:edit, locale_changeset: changeset)
end end
end end
@ -75,7 +75,7 @@ defmodule CanneryWeb.UserSettingsController do
:ok -> :ok ->
conn conn
|> put_flash(:info, dgettext("prompts", "Email changed successfully.")) |> put_flash(:info, dgettext("prompts", "Email changed successfully."))
|> redirect(to: Routes.user_settings_path(conn, :edit)) |> redirect(to: ~p"/users/settings")
:error -> :error ->
conn conn
@ -83,7 +83,7 @@ defmodule CanneryWeb.UserSettingsController do
:error, :error,
dgettext("errors", "Email change link is invalid or it has expired.") dgettext("errors", "Email change link is invalid or it has expired.")
) )
|> redirect(to: Routes.user_settings_path(conn, :edit)) |> redirect(to: ~p"/users/settings")
end end
end end
@ -93,11 +93,11 @@ defmodule CanneryWeb.UserSettingsController do
conn conn
|> put_flash(:error, dgettext("prompts", "Your account has been deleted")) |> put_flash(:error, dgettext("prompts", "Your account has been deleted"))
|> redirect(to: Routes.live_path(conn, HomeLive)) |> redirect(to: ~p"/")
else else
conn conn
|> put_flash(:error, dgettext("errors", "Unable to delete user")) |> put_flash(:error, dgettext("errors", "Unable to delete user"))
|> redirect(to: Routes.user_settings_path(conn, :edit)) |> redirect(to: ~p"/users/settings")
end end
end end

View File

@ -0,0 +1,5 @@
defmodule CanneryWeb.UserSettingsHTML do
use CanneryWeb, :html
embed_templates "user_settings_html/*"
end

View File

@ -8,7 +8,7 @@
<.form <.form
:let={f} :let={f}
for={@email_changeset} for={@email_changeset}
action={Routes.user_settings_path(@conn, :update)} action={~p"/users/settings"}
class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center" class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center"
> >
<h3 class="title text-primary-600 text-lg text-center col-span-3"> <h3 class="title text-primary-600 text-lg text-center col-span-3">
@ -50,7 +50,7 @@
<.form <.form
:let={f} :let={f}
for={@password_changeset} for={@password_changeset}
action={Routes.user_settings_path(@conn, :update)} action={~p"/users/settings"}
class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center" class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center"
> >
<h3 class="title text-primary-600 text-lg text-center col-span-3"> <h3 class="title text-primary-600 text-lg text-center col-span-3">
@ -104,7 +104,7 @@
<.form <.form
:let={f} :let={f}
for={@locale_changeset} for={@locale_changeset}
action={Routes.user_settings_path(@conn, :update)} action={~p"/users/settings"}
class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center" class="flex flex-col space-y-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 justify-center items-center"
> >
<%= label(f, :locale, dgettext("actions", "Change Language"), <%= label(f, :locale, dgettext("actions", "Change Language"),
@ -142,17 +142,13 @@
<hr class="hr" /> <hr class="hr" />
<div class="flex justify-center items-center"> <div class="flex justify-center items-center">
<.link <.link href={~p"/export/json"} class="mx-4 my-2 btn btn-primary" target="_blank">
href={Routes.export_path(@conn, :export, :json)}
class="mx-4 my-2 btn btn-primary"
target="_blank"
>
<%= dgettext("actions", "Export Data as JSON") %> <%= dgettext("actions", "Export Data as JSON") %>
</.link> </.link>
<.link <.link
href={Routes.user_settings_path(@conn, :delete, @current_user)} href={~p"/users/settings/#{@current_user}"}
method={:delete} method="delete"
class="mx-4 my-2 btn btn-alert" class="mx-4 my-2 btn btn-alert"
data-confirm={dgettext("prompts", "Are you sure you want to delete your account?")} data-confirm={dgettext("prompts", "Are you sure you want to delete your account?")}
> >

View File

@ -20,7 +20,7 @@ defmodule CanneryWeb.Endpoint do
at: "/", at: "/",
from: :cannery, from: :cannery,
gzip: false, gzip: false,
only: ~w(css fonts images js favicon.ico robots.txt) only: CanneryWeb.static_paths()
# Code reloading can be explicitly enabled under the # Code reloading can be explicitly enabled under the
# :code_reloader configuration of your endpoint. # :code_reloader configuration of your endpoint.

View File

@ -1,8 +1,6 @@
defmodule CanneryWeb.ViewHelpers do defmodule CanneryWeb.HTMLHelpers do
@moduledoc """ @moduledoc """
Contains common helpers that can be used in liveviews and regular views. These Contains common helpers that are used for rendering
are automatically imported into any Phoenix View using `use CanneryWeb,
:view`
""" """
use Phoenix.Component use Phoenix.Component

View File

@ -31,7 +31,6 @@
id: "container-form-desc", id: "container-form-desc",
class: "input input-primary col-span-2", class: "input input-primary col-span-2",
placeholder: gettext("Metal ammo can with the anime girl sticker"), placeholder: gettext("Metal ammo can with the anime girl sticker"),
phx_hook: "MaintainAttrs",
phx_update: "ignore" phx_update: "ignore"
) %> ) %>
<%= error_tag(f, :desc, "col-span-3 text-center") %> <%= error_tag(f, :desc, "col-span-3 text-center") %>
@ -49,7 +48,6 @@
id: "container-form-location", id: "container-form-location",
class: "input input-primary col-span-2", class: "input input-primary col-span-2",
placeholder: gettext("On the bookshelf"), placeholder: gettext("On the bookshelf"),
phx_hook: "MaintainAttrs",
phx_update: "ignore" phx_update: "ignore"
) %> ) %>
<%= error_tag(f, :location, "col-span-3 text-center") %> <%= error_tag(f, :location, "col-span-3 text-center") %>

View File

@ -105,12 +105,11 @@ defmodule CanneryWeb.ContainerLive.Index do
end end
def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do
{:noreply, socket |> push_patch(to: Routes.container_index_path(Endpoint, :index))} {:noreply, socket |> push_patch(to: ~p"/containers")}
end end
def handle_event("search", %{"search" => %{"search_term" => search_term}}, socket) do def handle_event("search", %{"search" => %{"search_term" => search_term}}, socket) do
{:noreply, {:noreply, socket |> push_patch(to: ~p"/containers/search/#{search_term}")}
socket |> push_patch(to: Routes.container_index_path(Endpoint, :search, search_term))}
end end
defp display_containers(%{assigns: %{search: search, current_user: current_user}} = socket) do defp display_containers(%{assigns: %{search: search, current_user: current_user}} = socket) do

View File

@ -9,11 +9,11 @@
<%= display_emoji("😔") %> <%= display_emoji("😔") %>
</h2> </h2>
<.link patch={Routes.container_index_path(Endpoint, :new)} class="btn btn-primary"> <.link patch={~p"/containers/new"} class="btn btn-primary">
<%= dgettext("actions", "Add your first container!") %> <%= dgettext("actions", "Add your first container!") %>
</.link> </.link>
<% else %> <% else %>
<.link patch={Routes.container_index_path(Endpoint, :new)} class="btn btn-primary"> <.link patch={~p"/containers/new"} class="btn btn-primary">
<%= dgettext("actions", "New Container") %> <%= dgettext("actions", "New Container") %>
</.link> </.link>
@ -59,7 +59,7 @@
<:tag_actions :let={container}> <:tag_actions :let={container}>
<div class="mx-4 my-2"> <div class="mx-4 my-2">
<.link <.link
patch={Routes.container_index_path(Endpoint, :edit_tags, container)} patch={~p"/containers/edit_tags/#{container}"}
class="text-primary-600 link" class="text-primary-600 link"
aria-label={ aria-label={
dgettext("actions", "Tag %{container_name}", container_name: container.name) dgettext("actions", "Tag %{container_name}", container_name: container.name)
@ -71,7 +71,7 @@
</:tag_actions> </:tag_actions>
<:actions :let={container}> <:actions :let={container}>
<.link <.link
patch={Routes.container_index_path(Endpoint, :edit, container)} patch={~p"/containers/edit/#{container}"}
class="text-primary-600 link" class="text-primary-600 link"
aria-label={ aria-label={
dgettext("actions", "Edit %{container_name}", container_name: container.name) dgettext("actions", "Edit %{container_name}", container_name: container.name)
@ -81,7 +81,7 @@
</.link> </.link>
<.link <.link
patch={Routes.container_index_path(Endpoint, :clone, container)} patch={~p"/containers/clone/#{container}"}
class="text-primary-600 link" class="text-primary-600 link"
aria-label={ aria-label={
dgettext("actions", "Clone %{container_name}", container_name: container.name) dgettext("actions", "Clone %{container_name}", container_name: container.name)
@ -118,7 +118,7 @@
<:tag_actions> <:tag_actions>
<div class="mx-4 my-2"> <div class="mx-4 my-2">
<.link <.link
patch={Routes.container_index_path(Endpoint, :edit_tags, container)} patch={~p"/containers/edit_tags/#{container}"}
class="text-primary-600 link" class="text-primary-600 link"
aria-label={ aria-label={
dgettext("actions", "Tag %{container_name}", container_name: container.name) dgettext("actions", "Tag %{container_name}", container_name: container.name)
@ -129,7 +129,7 @@
</div> </div>
</:tag_actions> </:tag_actions>
<.link <.link
patch={Routes.container_index_path(Endpoint, :edit, container)} patch={~p"/containers/edit/#{container}"}
class="text-primary-600 link" class="text-primary-600 link"
aria-label={ aria-label={
dgettext("actions", "Edit %{container_name}", container_name: container.name) dgettext("actions", "Edit %{container_name}", container_name: container.name)
@ -139,7 +139,7 @@
</.link> </.link>
<.link <.link
patch={Routes.container_index_path(Endpoint, :clone, container)} patch={~p"/containers/clone/#{container}"}
class="text-primary-600 link" class="text-primary-600 link"
aria-label={ aria-label={
dgettext("actions", "Clone %{container_name}", container_name: container.name) dgettext("actions", "Clone %{container_name}", container_name: container.name)
@ -173,26 +173,26 @@
<%= case @live_action do %> <%= case @live_action do %>
<% modifying when modifying in [:new, :edit, :clone] -> %> <% modifying when modifying in [:new, :edit, :clone] -> %>
<.modal return_to={Routes.container_index_path(Endpoint, :index)}> <.modal return_to={~p"/containers"}>
<.live_component <.live_component
module={CanneryWeb.ContainerLive.FormComponent} module={CanneryWeb.ContainerLive.FormComponent}
id={@container.id || :new} id={@container.id || :new}
title={@page_title} title={@page_title}
action={@live_action} action={@live_action}
container={@container} container={@container}
return_to={Routes.container_index_path(Endpoint, :index)} return_to={~p"/containers"}
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>
<% :edit_tags -> %> <% :edit_tags -> %>
<.modal return_to={Routes.container_index_path(Endpoint, :index)}> <.modal return_to={~p"/containers"}>
<.live_component <.live_component
module={CanneryWeb.ContainerLive.EditTagsComponent} module={CanneryWeb.ContainerLive.EditTagsComponent}
id={@container.id} id={@container.id}
title={@page_title} title={@page_title}
action={@live_action} action={@live_action}
container={@container} container={@container}
current_path={Routes.container_index_path(Endpoint, :edit_tags, @container)} current_path={~p"/containers/edit_tags/#{@container}"}
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>

View File

@ -5,7 +5,6 @@ defmodule CanneryWeb.ContainerLive.Show do
use CanneryWeb, :live_view use CanneryWeb, :live_view
alias Cannery.{Accounts.User, ActivityLog, Ammo, Containers, Containers.Container} alias Cannery.{Accounts.User, ActivityLog, Ammo, Containers, Containers.Container}
alias CanneryWeb.Endpoint
alias Ecto.Changeset alias Ecto.Changeset
alias Phoenix.LiveView.Socket alias Phoenix.LiveView.Socket
@ -59,10 +58,7 @@ defmodule CanneryWeb.ContainerLive.Show do
|> case do |> case do
{:ok, %{name: container_name}} -> {:ok, %{name: container_name}} ->
prompt = dgettext("prompts", "%{name} has been deleted", name: container_name) prompt = dgettext("prompts", "%{name} has been deleted", name: container_name)
socket |> put_flash(:info, prompt) |> push_navigate(to: ~p"/containers")
socket
|> put_flash(:info, prompt)
|> push_navigate(to: Routes.container_index_path(socket, :index))
{:error, %{action: :delete, errors: [packs: _error], valid?: false} = changeset} -> {:error, %{action: :delete, errors: [packs: _error], valid?: false} = changeset} ->
packs_error = changeset |> changeset_errors(:packs) |> Enum.join(", ") packs_error = changeset |> changeset_errors(:packs) |> Enum.join(", ")

View File

@ -30,7 +30,7 @@
<div class="flex space-x-4 justify-center items-center text-primary-600"> <div class="flex space-x-4 justify-center items-center text-primary-600">
<.link <.link
patch={Routes.container_show_path(Endpoint, :edit, @container)} patch={~p"/container/edit/#{@container}"}
class="text-primary-600 link" class="text-primary-600 link"
aria-label={dgettext("actions", "Edit %{container_name}", container_name: @container.name)} aria-label={dgettext("actions", "Edit %{container_name}", container_name: @container.name)}
> >
@ -61,10 +61,7 @@
<%= display_emoji("😔") %> <%= display_emoji("😔") %>
</h2> </h2>
<.link <.link patch={~p"/container/edit_tags/#{@container}"} class="btn btn-primary">
patch={Routes.container_show_path(Endpoint, :edit_tags, @container)}
class="btn btn-primary"
>
<%= dgettext("actions", "Why not add one?") %> <%= dgettext("actions", "Why not add one?") %>
</.link> </.link>
</div> </div>
@ -73,10 +70,7 @@
<.simple_tag_card :for={tag <- @container.tags} tag={tag} /> <.simple_tag_card :for={tag <- @container.tags} tag={tag} />
<div class="mx-4 my-2"> <div class="mx-4 my-2">
<.link <.link patch={~p"/container/edit_tags/#{@container}"} class="text-primary-600 link">
patch={Routes.container_show_path(Endpoint, :edit_tags, @container)}
class="text-primary-600 link"
>
<i class="fa-fw fa-lg fas fa-tags"></i> <i class="fa-fw fa-lg fas fa-tags"></i>
</.link> </.link>
</div> </div>
@ -132,7 +126,7 @@
show_used={false} show_used={false}
> >
<:type :let={%{name: type_name} = type}> <:type :let={%{name: type_name} = type}>
<.link navigate={Routes.type_show_path(Endpoint, :show, type)} class="link"> <.link navigate={~p"/type/#{type}"} class="link">
<%= type_name %> <%= type_name %>
</.link> </.link>
</:type> </:type>
@ -155,27 +149,27 @@
<%= case @live_action do %> <%= case @live_action do %>
<% :edit -> %> <% :edit -> %>
<.modal return_to={Routes.container_show_path(Endpoint, :show, @container)}> <.modal return_to={~p"/container/#{@container}"}>
<.live_component <.live_component
module={CanneryWeb.ContainerLive.FormComponent} module={CanneryWeb.ContainerLive.FormComponent}
id={@container.id} id={@container.id}
title={@page_title} title={@page_title}
action={@live_action} action={@live_action}
container={@container} container={@container}
return_to={Routes.container_show_path(Endpoint, :show, @container)} return_to={~p"/container/#{@container}"}
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>
<% :edit_tags -> %> <% :edit_tags -> %>
<.modal return_to={Routes.container_show_path(Endpoint, :show, @container)}> <.modal return_to={~p"/container/#{@container}"}>
<.live_component <.live_component
module={CanneryWeb.ContainerLive.EditTagsComponent} module={CanneryWeb.ContainerLive.EditTagsComponent}
id={@container.id} id={@container.id}
title={@page_title} title={@page_title}
action={@live_action} action={@live_action}
container={@container} container={@container}
return_to={Routes.container_show_path(Endpoint, :show, @container)} return_to={~p"/container/#{@container}"}
current_path={Routes.container_show_path(Endpoint, :edit_tags, @container)} current_path={~p"/container/edit_tags/#{@container}"}
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>

View File

@ -5,7 +5,6 @@ defmodule CanneryWeb.HomeLive do
use CanneryWeb, :live_view use CanneryWeb, :live_view
alias Cannery.Accounts alias Cannery.Accounts
alias CanneryWeb.Endpoint
@version Mix.Project.config()[:version] @version Mix.Project.config()[:version]

View File

@ -1,6 +1,6 @@
<div class="mx-auto px-8 sm:px-16 flex flex-col justify-center items-center text-center space-y-4 max-w-3xl"> <div class="mx-auto px-8 sm:px-16 flex flex-col justify-center items-center text-center space-y-4 max-w-3xl">
<img <img
src={Routes.static_path(Endpoint, "/images/cannery.svg")} src={~p"/images/cannery.svg"}
alt={gettext("Cannery logo")} alt={gettext("Cannery logo")}
class="inline-block w-32 hover:-mt-2 hover:mb-2 transition-all duration-500 ease-in-out" class="inline-block w-32 hover:-mt-2 hover:mb-2 transition-all duration-500 ease-in-out"
title={gettext("isn't he cute >:3")} title={gettext("isn't he cute >:3")}
@ -59,7 +59,7 @@
</b> </b>
<p> <p>
<%= if @admins |> Enum.empty?() do %> <%= if @admins |> Enum.empty?() do %>
<.link href={Routes.user_registration_path(Endpoint, :new)} class="hover:underline"> <.link href={~p"/users/register"} class="hover:underline">
<%= dgettext("prompts", "Register to setup Cannery") %> <%= dgettext("prompts", "Register to setup Cannery") %>
</.link> </.link>
<% else %> <% else %>

View File

@ -9,11 +9,11 @@
<%= display_emoji("😔") %> <%= display_emoji("😔") %>
</h1> </h1>
<.link patch={Routes.invite_index_path(Endpoint, :new)} class="btn btn-primary"> <.link patch={~p"/invites/new"} class="btn btn-primary">
<%= dgettext("actions", "Invite someone new!") %> <%= dgettext("actions", "Invite someone new!") %>
</.link> </.link>
<% else %> <% else %>
<.link patch={Routes.invite_index_path(Endpoint, :new)} class="btn btn-primary"> <.link patch={~p"/invites/new"} class="btn btn-primary">
<%= dgettext("actions", "Create Invite") %> <%= dgettext("actions", "Create Invite") %>
</.link> </.link>
<% end %> <% end %>
@ -40,7 +40,7 @@
</form> </form>
</:code_actions> </:code_actions>
<.link <.link
patch={Routes.invite_index_path(Endpoint, :edit, invite)} patch={~p"/invites/edit/#{invite}"}
class="text-primary-600 link" class="text-primary-600 link"
aria-label={ aria-label={
dgettext("actions", "Edit invite for %{invite_name}", invite_name: invite.name) dgettext("actions", "Edit invite for %{invite_name}", invite_name: invite.name)
@ -149,14 +149,14 @@
<% end %> <% end %>
</div> </div>
<.modal :if={@live_action in [:new, :edit]} return_to={Routes.invite_index_path(Endpoint, :index)}> <.modal :if={@live_action in [:new, :edit]} return_to={~p"/invites"}>
<.live_component <.live_component
module={CanneryWeb.InviteLive.FormComponent} module={CanneryWeb.InviteLive.FormComponent}
id={@invite.id || :new} id={@invite.id || :new}
title={@page_title} title={@page_title}
action={@live_action} action={@live_action}
invite={@invite} invite={@invite}
return_to={Routes.invite_index_path(Endpoint, :index)} return_to={~p"/invites"}
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>

View File

@ -58,7 +58,6 @@
<%= textarea(f, :notes, <%= textarea(f, :notes,
id: "pack-form-notes", id: "pack-form-notes",
class: "text-center col-span-2 input input-primary", class: "text-center col-span-2 input input-primary",
phx_hook: "MaintainAttrs",
phx_update: "ignore" phx_update: "ignore"
) %> ) %>
<%= error_tag(f, :notes, "col-span-3 text-center") %> <%= error_tag(f, :notes, "col-span-3 text-center") %>

View File

@ -113,13 +113,11 @@ defmodule CanneryWeb.PackLive.Index do
end end
def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do
{:noreply, socket |> push_patch(to: Routes.pack_index_path(Endpoint, :index))} {:noreply, socket |> push_patch(to: ~p"/ammo")}
end end
def handle_event("search", %{"search" => %{"search_term" => search_term}}, socket) do def handle_event("search", %{"search" => %{"search_term" => search_term}}, socket) do
socket = socket |> push_patch(to: Routes.pack_index_path(Endpoint, :search, search_term)) {:noreply, socket |> push_patch(to: ~p"/ammo/search/#{search_term}")}
{:noreply, socket}
end end
def handle_event("change_class", %{"type" => %{"class" => "rifle"}}, socket) do def handle_event("change_class", %{"type" => %{"class" => "rifle"}}, socket) do

View File

@ -10,7 +10,7 @@
<%= dgettext("prompts", "You'll need to") %> <%= dgettext("prompts", "You'll need to") %>
</h2> </h2>
<.link navigate={Routes.container_index_path(Endpoint, :new)} class="btn btn-primary"> <.link navigate={~p"/containers/new"} class="btn btn-primary">
<%= dgettext("actions", "add a container first") %> <%= dgettext("actions", "add a container first") %>
</.link> </.link>
</div> </div>
@ -20,7 +20,7 @@
<%= dgettext("prompts", "You'll need to") %> <%= dgettext("prompts", "You'll need to") %>
</h2> </h2>
<.link navigate={Routes.type_index_path(Endpoint, :new)} class="btn btn-primary"> <.link navigate={~p"/catalog/new"} class="btn btn-primary">
<%= dgettext("actions", "add a type first") %> <%= dgettext("actions", "add a type first") %>
</.link> </.link>
</div> </div>
@ -30,11 +30,11 @@
<%= display_emoji("😔") %> <%= display_emoji("😔") %>
</h2> </h2>
<.link patch={Routes.pack_index_path(Endpoint, :new)} class="btn btn-primary"> <.link patch={~p"/ammo/new"} class="btn btn-primary">
<%= dgettext("actions", "Add your first box!") %> <%= dgettext("actions", "Add your first box!") %>
</.link> </.link>
<% true -> %> <% true -> %>
<.link patch={Routes.pack_index_path(Endpoint, :new)} class="btn btn-primary"> <.link patch={~p"/ammo/new"} class="btn btn-primary">
<%= dgettext("actions", "Add Ammo") %> <%= dgettext("actions", "Add Ammo") %>
</.link> </.link>
@ -103,7 +103,7 @@
show_used={@show_used} show_used={@show_used}
> >
<:type :let={%{name: type_name} = type}> <:type :let={%{name: type_name} = type}>
<.link navigate={Routes.type_show_path(Endpoint, :show, type)} class="link"> <.link navigate={~p"/type/#{type}"} class="link">
<%= type_name %> <%= type_name %>
</.link> </.link>
</:type> </:type>
@ -121,7 +121,7 @@
</button> </button>
<.link <.link
patch={Routes.pack_index_path(Endpoint, :add_shot_record, pack)} patch={~p"/ammo/add_shot_record/#{pack}"}
class="mx-2 my-1 text-sm btn btn-primary" class="mx-2 my-1 text-sm btn btn-primary"
> >
<%= dgettext("actions", "Record shots") %> <%= dgettext("actions", "Record shots") %>
@ -130,17 +130,11 @@
</:range> </:range>
<:container :let={{pack, %{name: container_name} = container}}> <:container :let={{pack, %{name: container_name} = container}}>
<div class="min-w-20 py-2 px-4 h-full flex flew-wrap justify-center items-center"> <div class="min-w-20 py-2 px-4 h-full flex flew-wrap justify-center items-center">
<.link <.link navigate={~p"/container/#{container}"} class="mx-2 my-1 link">
navigate={Routes.container_show_path(Endpoint, :show, container)}
class="mx-2 my-1 link"
>
<%= container_name %> <%= container_name %>
</.link> </.link>
<.link <.link patch={~p"/ammo/move/#{pack}"} class="mx-2 my-1 text-sm btn btn-primary">
patch={Routes.pack_index_path(Endpoint, :move, pack)}
class="mx-2 my-1 text-sm btn btn-primary"
>
<%= dgettext("actions", "Move ammo") %> <%= dgettext("actions", "Move ammo") %>
</.link> </.link>
</div> </div>
@ -148,7 +142,7 @@
<:actions :let={%{count: pack_count} = pack}> <:actions :let={%{count: pack_count} = pack}>
<div class="py-2 px-4 h-full space-x-4 flex justify-center items-center"> <div class="py-2 px-4 h-full space-x-4 flex justify-center items-center">
<.link <.link
navigate={Routes.pack_show_path(Endpoint, :show, pack)} navigate={~p"/ammo/show/#{pack}"}
class="text-primary-600 link" class="text-primary-600 link"
aria-label={ aria-label={
dgettext("actions", "View pack of %{pack_count} bullets", pack_count: pack_count) dgettext("actions", "View pack of %{pack_count} bullets", pack_count: pack_count)
@ -158,7 +152,7 @@
</.link> </.link>
<.link <.link
patch={Routes.pack_index_path(Endpoint, :edit, pack)} patch={~p"/ammo/edit/#{pack}"}
class="text-primary-600 link" class="text-primary-600 link"
aria-label={ aria-label={
dgettext("actions", "Edit pack of %{pack_count} bullets", pack_count: pack_count) dgettext("actions", "Edit pack of %{pack_count} bullets", pack_count: pack_count)
@ -168,7 +162,7 @@
</.link> </.link>
<.link <.link
patch={Routes.pack_index_path(Endpoint, :clone, pack)} patch={~p"/ammo/clone/#{pack}"}
class="text-primary-600 link" class="text-primary-600 link"
aria-label={ aria-label={
dgettext("actions", "Clone pack of %{pack_count} bullets", dgettext("actions", "Clone pack of %{pack_count} bullets",
@ -202,38 +196,38 @@
<%= case @live_action do %> <%= case @live_action do %>
<% create when create in [:new, :edit, :clone] -> %> <% create when create in [:new, :edit, :clone] -> %>
<.modal return_to={Routes.pack_index_path(Endpoint, :index)}> <.modal return_to={~p"/ammo"}>
<.live_component <.live_component
module={CanneryWeb.PackLive.FormComponent} module={CanneryWeb.PackLive.FormComponent}
id={@pack.id || :new} id={@pack.id || :new}
title={@page_title} title={@page_title}
action={@live_action} action={@live_action}
pack={@pack} pack={@pack}
return_to={Routes.pack_index_path(Endpoint, :index)} return_to={~p"/ammo"}
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>
<% :add_shot_record -> %> <% :add_shot_record -> %>
<.modal return_to={Routes.pack_index_path(Endpoint, :index)}> <.modal return_to={~p"/ammo"}>
<.live_component <.live_component
module={CanneryWeb.Components.AddShotRecordComponent} module={CanneryWeb.Components.AddShotRecordComponent}
id={:new} id={:new}
title={@page_title} title={@page_title}
action={@live_action} action={@live_action}
pack={@pack} pack={@pack}
return_to={Routes.pack_index_path(Endpoint, :index)} return_to={~p"/ammo"}
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>
<% :move -> %> <% :move -> %>
<.modal return_to={Routes.pack_index_path(Endpoint, :index)}> <.modal return_to={~p"/ammo"}>
<.live_component <.live_component
module={CanneryWeb.Components.MovePackComponent} module={CanneryWeb.Components.MovePackComponent}
id={@pack.id} id={@pack.id}
title={@page_title} title={@page_title}
action={@live_action} action={@live_action}
pack={@pack} pack={@pack}
return_to={Routes.pack_index_path(Endpoint, :index)} return_to={~p"/ammo"}
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>

View File

@ -7,7 +7,6 @@ defmodule CanneryWeb.PackLive.Show do
alias Cannery.{ActivityLog, ActivityLog.ShotRecord} alias Cannery.{ActivityLog, ActivityLog.ShotRecord}
alias Cannery.{Ammo, Ammo.Pack} alias Cannery.{Ammo, Ammo.Pack}
alias Cannery.{ComparableDate, Containers} alias Cannery.{ComparableDate, Containers}
alias CanneryWeb.Endpoint
alias Phoenix.LiveView.Socket alias Phoenix.LiveView.Socket
@impl true @impl true
@ -53,7 +52,7 @@ defmodule CanneryWeb.PackLive.Show do
pack |> Ammo.delete_pack!(current_user) pack |> Ammo.delete_pack!(current_user)
prompt = dgettext("prompts", "Ammo deleted succesfully") prompt = dgettext("prompts", "Ammo deleted succesfully")
redirect_to = Routes.pack_index_path(socket, :index) redirect_to = ~p"/ammo"
{:noreply, socket |> put_flash(:info, prompt) |> push_navigate(to: redirect_to)} {:noreply, socket |> put_flash(:info, prompt) |> push_navigate(to: redirect_to)}
end end
@ -139,7 +138,7 @@ defmodule CanneryWeb.PackLive.Show do
~H""" ~H"""
<div class="px-4 py-2 space-x-4 flex justify-center items-center"> <div class="px-4 py-2 space-x-4 flex justify-center items-center">
<.link <.link
patch={Routes.pack_show_path(Endpoint, :edit_shot_record, @pack, @shot_record)} patch={~p"/ammo/show/#{@pack}/edit/#{@shot_record}"}
class="text-primary-600 link" class="text-primary-600 link"
aria-label={ aria-label={
dgettext("actions", "Edit shot record of %{shot_record_count} shots", dgettext("actions", "Edit shot record of %{shot_record_count} shots",

View File

@ -48,15 +48,12 @@
<div class="flex flex-col justify-center items-center"> <div class="flex flex-col justify-center items-center">
<div class="flex flex-wrap justify-center items-center text-primary-600"> <div class="flex flex-wrap justify-center items-center text-primary-600">
<.link <.link navigate={~p"/type/#{@pack.type}"} class="mx-4 my-2 btn btn-primary">
navigate={Routes.type_show_path(Endpoint, :show, @pack.type)}
class="mx-4 my-2 btn btn-primary"
>
<%= dgettext("actions", "View in Catalog") %> <%= dgettext("actions", "View in Catalog") %>
</.link> </.link>
<.link <.link
patch={Routes.pack_show_path(Endpoint, :edit, @pack)} patch={~p"/ammo/show/edit/#{@pack}"}
class="mx-4 my-2 text-primary-600 link" class="mx-4 my-2 text-primary-600 link"
aria-label={ aria-label={
dgettext("actions", "Edit pack of %{pack_count} bullets", pack_count: @pack.count) dgettext("actions", "Edit pack of %{pack_count} bullets", pack_count: @pack.count)
@ -85,14 +82,11 @@
else: dgettext("actions", "Stage for range") %> else: dgettext("actions", "Stage for range") %>
</button> </button>
<.link patch={Routes.pack_show_path(Endpoint, :move, @pack)} class="btn btn-primary"> <.link patch={~p"/ammo/show/move/#{@pack}"} class="btn btn-primary">
<%= dgettext("actions", "Move ammo") %> <%= dgettext("actions", "Move ammo") %>
</.link> </.link>
<.link <.link patch={~p"/ammo/show/add_shot_record/#{@pack}"} class="mx-4 my-2 btn btn-primary">
patch={Routes.pack_show_path(Endpoint, :add_shot_record, @pack)}
class="mx-4 my-2 btn btn-primary"
>
<%= dgettext("actions", "Record shots") %> <%= dgettext("actions", "Record shots") %>
</.link> </.link>
</div> </div>
@ -130,50 +124,50 @@
<%= case @live_action do %> <%= case @live_action do %>
<% :edit -> %> <% :edit -> %>
<.modal return_to={Routes.pack_show_path(Endpoint, :show, @pack)}> <.modal return_to={~p"/ammo/show/#{@pack}"}>
<.live_component <.live_component
module={CanneryWeb.PackLive.FormComponent} module={CanneryWeb.PackLive.FormComponent}
id={@pack.id} id={@pack.id}
title={@page_title} title={@page_title}
action={@live_action} action={@live_action}
pack={@pack} pack={@pack}
return_to={Routes.pack_show_path(Endpoint, :show, @pack)} return_to={~p"/ammo/show/#{@pack}"}
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>
<% :edit_shot_record -> %> <% :edit_shot_record -> %>
<.modal return_to={Routes.pack_show_path(Endpoint, :show, @pack)}> <.modal return_to={~p"/ammo/show/#{@pack}"}>
<.live_component <.live_component
module={CanneryWeb.RangeLive.FormComponent} module={CanneryWeb.RangeLive.FormComponent}
id={@shot_record.id} id={@shot_record.id}
title={@page_title} title={@page_title}
action={@live_action} action={@live_action}
shot_record={@shot_record} shot_record={@shot_record}
return_to={Routes.pack_show_path(Endpoint, :show, @pack)} return_to={~p"/ammo/show/#{@pack}"}
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>
<% :add_shot_record -> %> <% :add_shot_record -> %>
<.modal return_to={Routes.pack_show_path(Endpoint, :show, @pack)}> <.modal return_to={~p"/ammo/show/#{@pack}"}>
<.live_component <.live_component
module={CanneryWeb.Components.AddShotRecordComponent} module={CanneryWeb.Components.AddShotRecordComponent}
id={:new} id={:new}
title={@page_title} title={@page_title}
action={@live_action} action={@live_action}
pack={@pack} pack={@pack}
return_to={Routes.pack_show_path(Endpoint, :show, @pack)} return_to={~p"/ammo/show/#{@pack}"}
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>
<% :move -> %> <% :move -> %>
<.modal return_to={Routes.pack_show_path(Endpoint, :show, @pack)}> <.modal return_to={~p"/ammo/show/#{@pack}"}>
<.live_component <.live_component
module={CanneryWeb.Components.MovePackComponent} module={CanneryWeb.Components.MovePackComponent}
id={@pack.id} id={@pack.id}
title={@page_title} title={@page_title}
action={@live_action} action={@live_action}
pack={@pack} pack={@pack}
return_to={Routes.pack_show_path(Endpoint, :show, @pack)} return_to={~p"/ammo/show/#{@pack}"}
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>

View File

@ -33,7 +33,6 @@
class: "input input-primary col-span-2", class: "input input-primary col-span-2",
maxlength: 255, maxlength: 255,
placeholder: gettext("Really great weather"), placeholder: gettext("Really great weather"),
phx_hook: "MaintainAttrs",
phx_update: "ignore" phx_update: "ignore"
) %> ) %>
<%= error_tag(f, :notes, "col-span-3") %> <%= error_tag(f, :notes, "col-span-3") %>

View File

@ -5,7 +5,6 @@ defmodule CanneryWeb.RangeLive.Index do
use CanneryWeb, :live_view use CanneryWeb, :live_view
alias Cannery.{ActivityLog, ActivityLog.ShotRecord, Ammo} alias Cannery.{ActivityLog, ActivityLog.ShotRecord, Ammo}
alias CanneryWeb.Endpoint
alias Phoenix.LiveView.Socket alias Phoenix.LiveView.Socket
@impl true @impl true
@ -94,11 +93,11 @@ defmodule CanneryWeb.RangeLive.Index do
end end
def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do
{:noreply, socket |> push_patch(to: Routes.range_index_path(Endpoint, :index))} {:noreply, socket |> push_patch(to: ~p"/range")}
end end
def handle_event("search", %{"search" => %{"search_term" => search_term}}, socket) do def handle_event("search", %{"search" => %{"search_term" => search_term}}, socket) do
{:noreply, socket |> push_patch(to: Routes.range_index_path(Endpoint, :search, search_term))} {:noreply, socket |> push_patch(to: ~p"/range/search/#{search_term}")}
end end
def handle_event("change_class", %{"type" => %{"class" => "rifle"}}, socket) do def handle_event("change_class", %{"type" => %{"class" => "rifle"}}, socket) do

View File

@ -9,11 +9,11 @@
<%= display_emoji("😔") %> <%= display_emoji("😔") %>
</h1> </h1>
<.link navigate={Routes.pack_index_path(Endpoint, :index)} class="btn btn-primary"> <.link navigate={~p"/ammo"} class="btn btn-primary">
<%= dgettext("actions", "Why not get some ready to shoot?") %> <%= dgettext("actions", "Why not get some ready to shoot?") %>
</.link> </.link>
<% else %> <% else %>
<.link navigate={Routes.pack_index_path(Endpoint, :index)} class="btn btn-primary"> <.link navigate={~p"/ammo"} class="btn btn-primary">
<%= dgettext("actions", "Stage ammo") %> <%= dgettext("actions", "Stage ammo") %>
</.link> </.link>
@ -38,10 +38,7 @@
else: dgettext("actions", "Stage for range") %> else: dgettext("actions", "Stage for range") %>
</button> </button>
<.link <.link patch={~p"/range/add_shot_record/#{pack}"} class="btn btn-primary">
patch={Routes.range_index_path(Endpoint, :add_shot_record, pack)}
class="btn btn-primary"
>
<%= dgettext("actions", "Record shots") %> <%= dgettext("actions", "Record shots") %>
</.link> </.link>
</.pack_card> </.pack_card>
@ -132,7 +129,7 @@
<:actions :let={shot_record}> <:actions :let={shot_record}>
<div class="px-4 py-2 space-x-4 flex justify-center items-center"> <div class="px-4 py-2 space-x-4 flex justify-center items-center">
<.link <.link
patch={Routes.range_index_path(Endpoint, :edit, shot_record)} patch={~p"/range/edit/#{shot_record}"}
class="text-primary-600 link" class="text-primary-600 link"
aria-label={ aria-label={
dgettext("actions", "Edit shot record of %{shot_record_count} shots", dgettext("actions", "Edit shot record of %{shot_record_count} shots",
@ -168,26 +165,26 @@
<%= case @live_action do %> <%= case @live_action do %>
<% :edit -> %> <% :edit -> %>
<.modal return_to={Routes.range_index_path(Endpoint, :index)}> <.modal return_to={~p"/range"}>
<.live_component <.live_component
module={CanneryWeb.RangeLive.FormComponent} module={CanneryWeb.RangeLive.FormComponent}
id={@shot_record.id} id={@shot_record.id}
title={@page_title} title={@page_title}
action={@live_action} action={@live_action}
shot_record={@shot_record} shot_record={@shot_record}
return_to={Routes.range_index_path(Endpoint, :index)} return_to={~p"/range"}
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>
<% :add_shot_record -> %> <% :add_shot_record -> %>
<.modal return_to={Routes.range_index_path(Endpoint, :index)}> <.modal return_to={~p"/range"}>
<.live_component <.live_component
module={CanneryWeb.Components.AddShotRecordComponent} module={CanneryWeb.Components.AddShotRecordComponent}
id={:new} id={:new}
title={@page_title} title={@page_title}
action={@live_action} action={@live_action}
pack={@pack} pack={@pack}
return_to={Routes.range_index_path(Endpoint, :index)} return_to={~p"/range"}
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>

View File

@ -5,7 +5,7 @@ defmodule CanneryWeb.TagLive.Index do
use CanneryWeb, :live_view use CanneryWeb, :live_view
alias Cannery.{Containers, Containers.Tag} alias Cannery.{Containers, Containers.Tag}
alias CanneryWeb.ViewHelpers alias CanneryWeb.HTMLHelpers
@impl true @impl true
def mount(%{"search" => search}, _session, socket) do def mount(%{"search" => search}, _session, socket) do
@ -33,7 +33,7 @@ defmodule CanneryWeb.TagLive.Index do
socket socket
|> assign( |> assign(
page_title: gettext("New Tag"), page_title: gettext("New Tag"),
tag: %Tag{bg_color: ViewHelpers.random_color(), text_color: "#ffffff"} tag: %Tag{bg_color: HTMLHelpers.random_color(), text_color: "#ffffff"}
) )
end end
@ -67,11 +67,11 @@ defmodule CanneryWeb.TagLive.Index do
end end
def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do
{:noreply, socket |> push_patch(to: Routes.tag_index_path(Endpoint, :index))} {:noreply, socket |> push_patch(to: ~p"/tags")}
end end
def handle_event("search", %{"search" => %{"search_term" => search_term}}, socket) do def handle_event("search", %{"search" => %{"search_term" => search_term}}, socket) do
{:noreply, socket |> push_patch(to: Routes.tag_index_path(Endpoint, :search, search_term))} {:noreply, socket |> push_patch(to: ~p"/tags/search/#{search_term}")}
end end
defp display_tags(%{assigns: %{search: search, current_user: current_user}} = socket) do defp display_tags(%{assigns: %{search: search, current_user: current_user}} = socket) do

View File

@ -11,11 +11,11 @@
<%= display_emoji("😔") %> <%= display_emoji("😔") %>
</h2> </h2>
<.link patch={Routes.tag_index_path(Endpoint, :new)} class="btn btn-primary"> <.link patch={~p"/tags/new"} class="btn btn-primary">
<%= dgettext("actions", "Make your first tag!") %> <%= dgettext("actions", "Make your first tag!") %>
</.link> </.link>
<% else %> <% else %>
<.link patch={Routes.tag_index_path(Endpoint, :new)} class="btn btn-primary"> <.link patch={~p"/tags/new"} class="btn btn-primary">
<%= dgettext("actions", "New Tag") %> <%= dgettext("actions", "New Tag") %>
</.link> </.link>
@ -47,7 +47,7 @@
<div class="flex flex-row flex-wrap justify-center items-stretch"> <div class="flex flex-row flex-wrap justify-center items-stretch">
<.tag_card :for={tag <- @tags} tag={tag}> <.tag_card :for={tag <- @tags} tag={tag}>
<.link <.link
patch={Routes.tag_index_path(Endpoint, :edit, tag)} patch={~p"/tags/edit/#{tag}"}
class="text-primary-600 link" class="text-primary-600 link"
aria-label={dgettext("actions", "Edit %{tag_name}", tag_name: tag.name)} aria-label={dgettext("actions", "Edit %{tag_name}", tag_name: tag.name)}
> >
@ -72,14 +72,14 @@
<% end %> <% end %>
</div> </div>
<.modal :if={@live_action in [:new, :edit]} return_to={Routes.tag_index_path(Endpoint, :index)}> <.modal :if={@live_action in [:new, :edit]} return_to={~p"/tags"}>
<.live_component <.live_component
module={CanneryWeb.TagLive.FormComponent} module={CanneryWeb.TagLive.FormComponent}
id={@tag.id || :new} id={@tag.id || :new}
title={@page_title} title={@page_title}
action={@live_action} action={@live_action}
tag={@tag} tag={@tag}
return_to={Routes.tag_index_path(Endpoint, :index)} return_to={~p"/tags"}
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>

View File

@ -39,7 +39,6 @@
<%= textarea(f, :desc, <%= textarea(f, :desc,
id: "type-form-desc", id: "type-form-desc",
class: "text-center col-span-2 input input-primary", class: "text-center col-span-2 input input-primary",
phx_hook: "MaintainAttrs",
phx_update: "ignore" phx_update: "ignore"
) %> ) %>
<%= error_tag(f, :desc, "col-span-3 text-center") %> <%= error_tag(f, :desc, "col-span-3 text-center") %>

View File

@ -78,12 +78,11 @@ defmodule CanneryWeb.TypeLive.Index do
end end
def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do
{:noreply, socket |> push_patch(to: Routes.type_index_path(Endpoint, :index))} {:noreply, socket |> push_patch(to: ~p"/catalog")}
end end
def handle_event("search", %{"search" => %{"search_term" => search_term}}, socket) do def handle_event("search", %{"search" => %{"search_term" => search_term}}, socket) do
search_path = Routes.type_index_path(Endpoint, :search, search_term) {:noreply, socket |> push_patch(to: ~p"/catalog/search/#{search_term}")}
{:noreply, socket |> push_patch(to: search_path)}
end end
def handle_event("change_class", %{"type" => %{"class" => "rifle"}}, socket) do def handle_event("change_class", %{"type" => %{"class" => "rifle"}}, socket) do

View File

@ -9,11 +9,11 @@
<%= display_emoji("😔") %> <%= display_emoji("😔") %>
</h2> </h2>
<.link patch={Routes.type_index_path(Endpoint, :new)} class="btn btn-primary"> <.link patch={~p"/catalog/new"} class="btn btn-primary">
<%= dgettext("actions", "Add your first type!") %> <%= dgettext("actions", "Add your first type!") %>
</.link> </.link>
<% else %> <% else %>
<.link patch={Routes.type_index_path(Endpoint, :new)} class="btn btn-primary"> <.link patch={~p"/catalog/new"} class="btn btn-primary">
<%= dgettext("actions", "New Type") %> <%= dgettext("actions", "New Type") %>
</.link> </.link>
@ -84,7 +84,7 @@
<:actions :let={type}> <:actions :let={type}>
<div class="px-4 py-2 space-x-4 flex justify-center items-center"> <div class="px-4 py-2 space-x-4 flex justify-center items-center">
<.link <.link
navigate={Routes.type_show_path(Endpoint, :show, type)} navigate={~p"/type/#{type}"}
class="text-primary-600 link" class="text-primary-600 link"
aria-label={dgettext("actions", "View %{type_name}", type_name: type.name)} aria-label={dgettext("actions", "View %{type_name}", type_name: type.name)}
> >
@ -92,7 +92,7 @@
</.link> </.link>
<.link <.link
patch={Routes.type_index_path(Endpoint, :edit, type)} patch={~p"/catalog/edit/#{type}"}
class="text-primary-600 link" class="text-primary-600 link"
aria-label={dgettext("actions", "Edit %{type_name}", type_name: type.name)} aria-label={dgettext("actions", "Edit %{type_name}", type_name: type.name)}
> >
@ -100,7 +100,7 @@
</.link> </.link>
<.link <.link
patch={Routes.type_index_path(Endpoint, :clone, type)} patch={~p"/catalog/clone/#{type}"}
class="text-primary-600 link" class="text-primary-600 link"
aria-label={dgettext("actions", "Clone %{type_name}", type_name: type.name)} aria-label={dgettext("actions", "Clone %{type_name}", type_name: type.name)}
> >
@ -130,17 +130,14 @@
<% end %> <% end %>
</div> </div>
<.modal <.modal :if={@live_action in [:new, :edit, :clone]} return_to={~p"/catalog"}>
:if={@live_action in [:new, :edit, :clone]}
return_to={Routes.type_index_path(Endpoint, :index)}
>
<.live_component <.live_component
module={CanneryWeb.TypeLive.FormComponent} module={CanneryWeb.TypeLive.FormComponent}
id={@type.id || :new} id={@type.id || :new}
title={@page_title} title={@page_title}
action={@live_action} action={@live_action}
type={@type} type={@type}
return_to={Routes.type_index_path(Endpoint, :index)} return_to={~p"/catalog"}
current_user={@current_user} current_user={@current_user}
} }
/> />

View File

@ -5,7 +5,6 @@ defmodule CanneryWeb.TypeLive.Show do
use CanneryWeb, :live_view use CanneryWeb, :live_view
alias Cannery.{ActivityLog, Ammo, Ammo.Type, Containers} alias Cannery.{ActivityLog, Ammo, Ammo.Type, Containers}
alias CanneryWeb.Endpoint
@impl true @impl true
def mount(_params, _session, socket), def mount(_params, _session, socket),
@ -25,7 +24,7 @@ defmodule CanneryWeb.TypeLive.Show do
%{name: type_name} = type |> Ammo.delete_type!(current_user) %{name: type_name} = type |> Ammo.delete_type!(current_user)
prompt = dgettext("prompts", "%{name} deleted succesfully", name: type_name) prompt = dgettext("prompts", "%{name} deleted succesfully", name: type_name)
redirect_to = Routes.type_index_path(socket, :index) redirect_to = ~p"/catalog"
{:noreply, socket |> put_flash(:info, prompt) |> push_navigate(to: redirect_to)} {:noreply, socket |> put_flash(:info, prompt) |> push_navigate(to: redirect_to)}
end end

View File

@ -14,7 +14,7 @@
<div class="flex space-x-4 justify-center items-center text-primary-600"> <div class="flex space-x-4 justify-center items-center text-primary-600">
<.link <.link
patch={Routes.type_show_path(Endpoint, :edit, @type)} patch={~p"/type/#{@type}/edit"}
class="text-primary-600 link" class="text-primary-600 link"
aria-label={dgettext("actions", "Edit %{type_name}", type_name: @type.name)} aria-label={dgettext("actions", "Edit %{type_name}", type_name: @type.name)}
> >
@ -188,17 +188,14 @@
show_used={@show_used} show_used={@show_used}
> >
<:container :let={{_pack, %{name: container_name} = container}}> <:container :let={{_pack, %{name: container_name} = container}}>
<.link <.link navigate={~p"/container/#{container}"} class="mx-2 my-1 link">
navigate={Routes.container_show_path(Endpoint, :show, container)}
class="mx-2 my-1 link"
>
<%= container_name %> <%= container_name %>
</.link> </.link>
</:container> </:container>
<:actions :let={%{count: pack_count} = pack}> <:actions :let={%{count: pack_count} = pack}>
<div class="py-2 px-4 h-full space-x-4 flex justify-center items-center"> <div class="py-2 px-4 h-full space-x-4 flex justify-center items-center">
<.link <.link
navigate={Routes.pack_show_path(Endpoint, :show, pack)} navigate={~p"/ammo/show/#{pack}"}
class="text-primary-600 link" class="text-primary-600 link"
aria-label={ aria-label={
dgettext("actions", "View pack of %{pack_count} bullets", pack_count: pack_count) dgettext("actions", "View pack of %{pack_count} bullets", pack_count: pack_count)
@ -226,14 +223,14 @@
</div> </div>
</div> </div>
<.modal :if={@live_action == :edit} return_to={Routes.type_show_path(Endpoint, :show, @type)}> <.modal :if={@live_action == :edit} return_to={~p"/type/#{@type}"}>
<.live_component <.live_component
module={CanneryWeb.TypeLive.FormComponent} module={CanneryWeb.TypeLive.FormComponent}
id={@type.id} id={@type.id}
title={@page_title} title={@page_title}
action={@live_action} action={@live_action}
type={@type} type={@type}
return_to={Routes.type_show_path(Endpoint, :show, @type)} return_to={~p"/type/#{@type}"}
current_user={@current_user} current_user={@current_user}
/> />
</.modal> </.modal>

View File

@ -7,25 +7,13 @@ defmodule CanneryWeb.Router do
plug :accepts, ["html"] plug :accepts, ["html"]
plug :fetch_session plug :fetch_session
plug :fetch_live_flash plug :fetch_live_flash
plug :put_root_layout, {CanneryWeb.LayoutView, :root} plug :put_root_layout, {CanneryWeb.Layouts, :root}
plug :protect_from_forgery plug :protect_from_forgery
plug :put_secure_browser_headers plug :put_secure_browser_headers
plug :fetch_current_user plug :fetch_current_user
plug :put_user_locale plug :put_user_locale
end end
defp put_user_locale(%{assigns: %{current_user: %{locale: locale}}} = conn, _opts) do
default = Application.fetch_env!(:gettext, :default_locale)
Gettext.put_locale(locale || default)
conn |> put_session(:locale, locale || default)
end
defp put_user_locale(conn, _opts) do
default = Application.fetch_env!(:gettext, :default_locale)
Gettext.put_locale(default)
conn |> put_session(:locale, default)
end
pipeline :require_admin do pipeline :require_admin do
plug :require_role, role: :admin plug :require_role, role: :admin
end end
@ -34,14 +22,6 @@ defmodule CanneryWeb.Router do
plug :accepts, ["json"] plug :accepts, ["json"]
end end
scope "/", CanneryWeb do
pipe_through :browser
live "/", HomeLive
end
## Authentication routes
scope "/", CanneryWeb do scope "/", CanneryWeb do
pipe_through [:browser, :redirect_if_user_is_authenticated] pipe_through [:browser, :redirect_if_user_is_authenticated]
@ -64,6 +44,7 @@ defmodule CanneryWeb.Router do
get "/users/settings/confirm_email/:token", UserSettingsController, :confirm_email get "/users/settings/confirm_email/:token", UserSettingsController, :confirm_email
get "/export/:mode", ExportController, :export get "/export/:mode", ExportController, :export
live_session :default, on_mount: [{CanneryWeb.UserAuth, :ensure_authenticated}] do
live "/tags", TagLive.Index, :index live "/tags", TagLive.Index, :index
live "/tags/new", TagLive.Index, :new live "/tags/new", TagLive.Index, :new
live "/tags/edit/:id", TagLive.Index, :edit live "/tags/edit/:id", TagLive.Index, :edit
@ -108,15 +89,6 @@ defmodule CanneryWeb.Router do
live "/range/add_shot_record/:id", RangeLive.Index, :add_shot_record live "/range/add_shot_record/:id", RangeLive.Index, :add_shot_record
live "/range/search/:search", RangeLive.Index, :search live "/range/search/:search", RangeLive.Index, :search
end end
scope "/", CanneryWeb do
pipe_through [:browser, :require_authenticated_user, :require_admin]
live_dashboard "/dashboard", metrics: CanneryWeb.Telemetry, ecto_repos: [Cannery.Repo]
live "/invites", InviteLive.Index, :index
live "/invites/new", InviteLive.Index, :new
live "/invites/:id/edit", InviteLive.Index, :edit
end end
scope "/", CanneryWeb do scope "/", CanneryWeb do
@ -126,6 +98,22 @@ defmodule CanneryWeb.Router do
get "/users/confirm", UserConfirmationController, :new get "/users/confirm", UserConfirmationController, :new
post "/users/confirm", UserConfirmationController, :create post "/users/confirm", UserConfirmationController, :create
get "/users/confirm/:token", UserConfirmationController, :confirm get "/users/confirm/:token", UserConfirmationController, :confirm
live_session :public, on_mount: [{CanneryWeb.UserAuth, :mount_current_user}] do
live "/", HomeLive
end
end
scope "/", CanneryWeb do
pipe_through [:browser, :require_authenticated_user, :require_admin]
live_dashboard "/dashboard", metrics: CanneryWeb.Telemetry, ecto_repos: [Cannery.Repo]
live_session :admin, on_mount: [{CanneryWeb.UserAuth, :ensure_admin}] do
live "/invites", InviteLive.Index, :index
live "/invites/new", InviteLive.Index, :new
live "/invites/edit/:id", InviteLive.Index, :edit
end
end end
# Enables the Swoosh mailbox preview in development. # Enables the Swoosh mailbox preview in development.
@ -137,9 +125,6 @@ defmodule CanneryWeb.Router do
pipe_through :browser pipe_through :browser
forward "/mailbox", Plug.Swoosh.MailboxPreview forward "/mailbox", Plug.Swoosh.MailboxPreview
end
scope "/dev" do
get "/preview/:id", CanneryWeb.EmailController, :preview get "/preview/:id", CanneryWeb.EmailController, :preview
end end
end end

View File

@ -1 +0,0 @@
<%= @inner_content %>

View File

@ -1,7 +0,0 @@
defmodule CanneryWeb.EmailView do
@moduledoc """
A view for email-related helper functions
"""
use CanneryWeb, :view
alias CanneryWeb.HomeLive
end

View File

@ -1,15 +0,0 @@
defmodule CanneryWeb.ErrorView do
use CanneryWeb, :view
alias CanneryWeb.HomeLive
def template_not_found(error_path, _assigns) do
error_string =
case error_path do
"404.html" -> dgettext("errors", "Not found")
"401.html" -> dgettext("errors", "Unauthorized")
_other_path -> dgettext("errors", "Internal Server Error")
end
render("error.html", %{error_string: error_string})
end
end

View File

@ -1,3 +0,0 @@
defmodule CanneryWeb.HomeView do
use CanneryWeb, :view
end

View File

@ -1,16 +0,0 @@
defmodule CanneryWeb.LayoutView do
use CanneryWeb, :view
alias CanneryWeb.HomeLive
# Phoenix LiveDashboard is available only in development by default,
# so we instruct Elixir to not warn if the dashboard route is missing.
@compile {:no_warn_undefined, {Routes, :live_dashboard_path, 2}}
def get_title(%{assigns: %{title: title}}) when title not in [nil, ""] do
gettext("Cannery | %{title}", title: title)
end
def get_title(_conn) do
gettext("Cannery")
end
end

View File

@ -1,4 +0,0 @@
defmodule CanneryWeb.UserConfirmationView do
use CanneryWeb, :view
alias Cannery.Accounts
end

View File

@ -1,3 +0,0 @@
defmodule CanneryWeb.UserRegistrationView do
use CanneryWeb, :view
end

View File

@ -1,4 +0,0 @@
defmodule CanneryWeb.UserResetPasswordView do
use CanneryWeb, :view
alias Cannery.Accounts
end

View File

@ -1,4 +0,0 @@
defmodule CanneryWeb.UserSessionView do
use CanneryWeb, :view
alias Cannery.Accounts
end

View File

@ -1,3 +0,0 @@
defmodule CanneryWeb.UserSettingsView do
use CanneryWeb, :view
end

View File

@ -4,10 +4,9 @@ defmodule Cannery.MixProject do
def project do def project do
[ [
app: :cannery, app: :cannery,
version: "0.9.2", version: "0.9.3",
elixir: "1.14.1", elixir: "1.14.4",
elixirc_paths: elixirc_paths(Mix.env()), elixirc_paths: elixirc_paths(Mix.env()),
compilers: Mix.compilers(),
start_permanent: Mix.env() == :prod, start_permanent: Mix.env() == :prod,
aliases: aliases(), aliases: aliases(),
deps: deps(), deps: deps(),
@ -48,12 +47,11 @@ defmodule Cannery.MixProject do
defp deps do defp deps do
[ [
{:bcrypt_elixir, "~> 3.0"}, {:bcrypt_elixir, "~> 3.0"},
{:phoenix, "~> 1.6.0"}, {:phoenix, "~> 1.7.0"},
{:phoenix_ecto, "~> 4.4"}, {:phoenix_ecto, "~> 4.4"},
{:phoenix_html, "~> 3.0"}, {:phoenix_html, "~> 3.0"},
{:phoenix_live_reload, "~> 1.2", only: :dev}, {:phoenix_live_reload, "~> 1.2", only: :dev},
{:phoenix_live_view, "~> 0.18.0"}, {:phoenix_live_view, "~> 0.18.0"},
{:phoenix_view, "~> 2.0"},
{:phoenix_live_dashboard, "~> 0.6"}, {:phoenix_live_dashboard, "~> 0.6"},
{:ecto_sql, "~> 3.6"}, {:ecto_sql, "~> 3.6"},
{:postgrex, ">= 0.0.0"}, {:postgrex, ">= 0.0.0"},
@ -62,7 +60,6 @@ defmodule Cannery.MixProject do
{:ex_doc, "~> 0.27", only: :dev, runtime: false}, {:ex_doc, "~> 0.27", only: :dev, runtime: false},
{:swoosh, "~> 1.6"}, {:swoosh, "~> 1.6"},
{:gen_smtp, "~> 1.0"}, {:gen_smtp, "~> 1.0"},
{:phoenix_swoosh, "~> 1.0"},
{:oban, "~> 2.10"}, {:oban, "~> 2.10"},
{:telemetry_metrics, "~> 0.6"}, {:telemetry_metrics, "~> 0.6"},
{:telemetry_poller, "~> 1.0"}, {:telemetry_poller, "~> 1.0"},

View File

@ -1,47 +1,45 @@
%{ %{
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.0.1", "9be815469e6bfefec40fa74658ecbbe6897acfb57614df1416eeccd4903f602c", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "486bb95efb645d1efc6794c1ddd776a186a9a713abf06f45708a6ce324fb96cf"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.0.1", "9be815469e6bfefec40fa74658ecbbe6897acfb57614df1416eeccd4903f602c", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "486bb95efb645d1efc6794c1ddd776a186a9a713abf06f45708a6ce324fb96cf"},
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
"castore": {:hex, :castore, "0.1.22", "4127549e411bedd012ca3a308dede574f43819fe9394254ca55ab4895abfa1a2", [:mix], [], "hexpm", "c17576df47eb5aa1ee40cc4134316a99f5cad3e215d5c77b8dd3cfef12a22cac"}, "castore": {:hex, :castore, "1.0.1", "240b9edb4e9e94f8f56ab39d8d2d0a57f49e46c56aced8f873892df8ff64ff5a", [:mix], [], "hexpm", "b4951de93c224d44fac71614beabd88b71932d0b1dea80d2f80fb9044e01bbb3"},
"comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"}, "comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"},
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
"cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"}, "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
"cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"}, "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
"credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"}, "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"},
"db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"}, "db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"},
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
"dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"}, "dialyxir": {:hex, :dialyxir, "1.3.0", "fd1672f0922b7648ff9ce7b1b26fcf0ef56dda964a459892ad15f6b4410b5284", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "00b2a4bcd6aa8db9dcb0b38c1225b7277dca9bc370b6438715667071a304696f"},
"earmark_parser": {:hex, :earmark_parser, "1.4.31", "a93921cdc6b9b869f519213d5bc79d9e218ba768d7270d46fdcf1c01bacff9e2", [:mix], [], "hexpm", "317d367ee0335ef037a87e46c91a2269fef6306413f731e8ec11fc45a7efd059"}, "earmark_parser": {:hex, :earmark_parser, "1.4.31", "a93921cdc6b9b869f519213d5bc79d9e218ba768d7270d46fdcf1c01bacff9e2", [:mix], [], "hexpm", "317d367ee0335ef037a87e46c91a2269fef6306413f731e8ec11fc45a7efd059"},
"ecto": {:hex, :ecto, "3.9.4", "3ee68e25dbe0c36f980f1ba5dd41ee0d3eb0873bccae8aeaf1a2647242bffa35", [: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", "de5f988c142a3aa4ec18b85a4ec34a2390b65b24f02385c1144252ff6ff8ee75"}, "ecto": {:hex, :ecto, "3.10.1", "c6757101880e90acc6125b095853176a02da8f1afe056f91f1f90b80c9389822", [: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", "d2ac4255f1601bdf7ac74c0ed971102c6829dc158719b94bd30041bbad77f87a"},
"ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.10", "e14d400930f401ca9f541b3349212634e44027d7f919bbb71224d7ac0d0e8acd", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.15.7 or ~> 0.16.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "505e8cd81e4f17c090be0f99e92b1b3f0fd915f98e76965130b8ccfb891e7088"}, "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.10", "e14d400930f401ca9f541b3349212634e44027d7f919bbb71224d7ac0d0e8acd", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.15.7 or ~> 0.16.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "505e8cd81e4f17c090be0f99e92b1b3f0fd915f98e76965130b8ccfb891e7088"},
"ecto_sql": {:hex, :ecto_sql, "3.9.2", "34227501abe92dba10d9c3495ab6770e75e79b836d114c41108a4bf2ce200ad5", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 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", "1eb5eeb4358fdbcd42eac11c1fbd87e3affd7904e639d77903c1358b2abd3f70"}, "ecto_sql": {:hex, :ecto_sql, "3.10.1", "6ea6b3036a0b0ca94c2a02613fd9f742614b5cfe494c41af2e6571bb034dd94c", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 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", "f6a25bdbbd695f12c8171eaff0851fa4c8e72eec1e98c7364402dda9ce11c56b"},
"elixir_make": {:hex, :elixir_make, "0.7.5", "784cc00f5fa24239067cc04d449437dcc5f59353c44eb08f188b2b146568738a", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "c3d63e8d5c92fa3880d89ecd41de59473fa2e83eeb68148155e25e8b95aa2887"}, "elixir_make": {:hex, :elixir_make, "0.7.6", "67716309dc5d43e16b5abbd00c01b8df6a0c2ab54a8f595468035a50189f9169", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5a0569756b0f7873a77687800c164cca6dfc03a09418e6fcf853d78991f49940"},
"eqrcode": {:hex, :eqrcode, "0.1.10", "6294fece9d68ad64eef1c3c92cf111cfd6469f4fbf230a2d4cc905a682178f3f", [:mix], [], "hexpm", "da30e373c36a0fd37ab6f58664b16029919896d6c45a68a95cc4d713e81076f1"}, "eqrcode": {:hex, :eqrcode, "0.1.10", "6294fece9d68ad64eef1c3c92cf111cfd6469f4fbf230a2d4cc905a682178f3f", [:mix], [], "hexpm", "da30e373c36a0fd37ab6f58664b16029919896d6c45a68a95cc4d713e81076f1"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"ex_doc": {:hex, :ex_doc, "0.29.2", "dfa97532ba66910b2a3016a4bbd796f41a86fc71dd5227e96f4c8581fdf0fdf0", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "6b5d7139eda18a753e3250e27e4a929f8d2c880dd0d460cb9986305dea3e03af"}, "ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"},
"expo": {:hex, :expo, "0.4.0", "bbe4bf455e2eb2ebd2f1e7d83530ce50fb9990eb88fc47855c515bfdf1c6626f", [:mix], [], "hexpm", "a8ed1683ec8b7c7fa53fd7a41b2c6935f539168a6bb0616d7fd6b58a36f3abf2"}, "expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"},
"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.34.2", "5fad07ef153b3b8ec110b6b155ec3780c4b2c4906297d0b4be1a7162d04a7e02", [:mix], [], "hexpm", "26b9d50f0f01796bc6be611ca815c5e0de034d2128e39cc9702eee6b66a4d1c8"}, "floki": {:hex, :floki, "0.34.2", "5fad07ef153b3b8ec110b6b155ec3780c4b2c4906297d0b4be1a7162d04a7e02", [:mix], [], "hexpm", "26b9d50f0f01796bc6be611ca815c5e0de034d2128e39cc9702eee6b66a4d1c8"},
"gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"}, "gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"},
"gettext": {:hex, :gettext, "0.22.1", "e7942988383c3d9eed4bdc22fc63e712b655ae94a672a27e4900e3d4a2c43581", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "ad105b8dab668ee3f90c0d3d94ba75e9aead27a62495c101d94f2657a190ac5d"}, "gettext": {:hex, :gettext, "0.22.1", "e7942988383c3d9eed4bdc22fc63e712b655ae94a672a27e4900e3d4a2c43581", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "ad105b8dab668ee3f90c0d3d94ba75e9aead27a62495c101d94f2657a190ac5d"},
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, "nimble_parsec": {:hex, :nimble_parsec, "1.3.0", "9e18a119d9efc3370a3ef2a937bf0b24c088d9c4bf0ba9d7c3751d49d347d035", [:mix], [], "hexpm", "7977f183127a7cbe9346981e2f480dc04c55ffddaef746bd58debd566070eef8"},
"oban": {:hex, :oban, "2.14.2", "ae925d9a33e110addaa59ff7ec1b2fd84270ac7eb00fbb4b4a179d74c407bba3", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "32bf30127c8c44ac42f05f229a50fadc2177b3e799c29499f5daf90d5e5b5d3c"}, "oban": {:hex, :oban, "2.15.0", "27b9c2845cdff30c98c8060b11a64318e79bbc1bd32b8dc95fa59a1580a8d90c", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "22e181c540335d1dd5c995be00435927075519207d62b3de32477d95dbf9dfd3"},
"phoenix": {:hex, :phoenix, "1.6.16", "e5bdd18c7a06da5852a25c7befb72246de4ddc289182285f8685a40b7b5f5451", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e15989ff34f670a96b95ef6d1d25bad0d9c50df5df40b671d8f4a669e050ac39"}, "phoenix": {:hex, :phoenix, "1.7.2", "c375ffb482beb4e3d20894f84dd7920442884f5f5b70b9f4528cbe0cedefec63", [: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.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.4", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "1ebca94b32b4d0e097ab2444a9742ed8ff3361acad17365e4e6b2e79b4792159"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"},
"phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"}, "phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [: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]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [: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]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{: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", [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]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"}, "phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{: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", [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]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.0", "a544d83fde4a767efb78f45404a74c9e37b2a9c5ea3339692e65a6966731f935", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "e88d117251e89a16b92222415a6d87b99a96747ddf674fc5c7631de734811dba"},
"phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"}, "phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"},
"phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"}, "plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"},
"plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"},
"plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [: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", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"}, "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [: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", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"},
"plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
"postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"}, "postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"},
@ -51,4 +49,6 @@
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
"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"},
"websock": {:hex, :websock, "0.5.0", "f6bbce90226121d62a0715bca7c986c5e43de0ccc9475d79c55381d1796368cc", [:mix], [], "hexpm", "b51ac706df8a7a48a2c622ee02d09d68be8c40418698ffa909d73ae207eb5fb8"},
"websock_adapter": {:hex, :websock_adapter, "0.5.0", "cea35d8bbf1a6964e32d4b02ceb561dfb769c04f16d60d743885587e7d2ca55b", [:mix], [{:bandit, "~> 0.6", [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", "16318b124effab8209b1eb7906c636374f623dc9511a8278ad09c083cea5bb83"},
} }

Some files were not shown because too many files have changed in this diff Show More