upgrade to phoenix 1.7
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
shibao 2023-04-13 23:29:29 -04:00
parent a1c846be33
commit 63d854ffbe
116 changed files with 1156 additions and 1111 deletions

View File

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

View File

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

View File

@ -7,10 +7,11 @@ defmodule Memex.Email do
`lib/memex_web/templates/layout/email.txt.heex` for text emails.
"""
use Phoenix.Swoosh, view: MemexWeb.EmailView, layout: {MemexWeb.LayoutView, :email}
import Swoosh.Email
import MemexWeb.Gettext
import Phoenix.Template
alias Memex.Accounts.User
alias MemexWeb.EmailView
alias MemexWeb.{EmailHTML, Layouts}
@typedoc """
Represents an HTML and text body email that can be sent
@ -28,21 +29,33 @@ defmodule Memex.Email do
def generate_email("welcome", user, %{"url" => url}) do
user
|> base_email(dgettext("emails", "Confirm your Memex account"))
|> render_body("confirm_email.html", %{user: user, url: url})
|> text_body(EmailView.render("confirm_email.txt", %{user: user, url: url}))
|> html_email(:confirm_email_html, %{user: user, url: url})
|> text_email(:confirm_email_text, %{user: user, url: url})
end
def generate_email("reset_password", user, %{"url" => url}) do
user
|> base_email(dgettext("emails", "Reset your Memex password"))
|> render_body("reset_password.html", %{user: user, url: url})
|> text_body(EmailView.render("reset_password.txt", %{user: user, url: url}))
|> html_email(:reset_password_html, %{user: user, url: url})
|> text_email(:reset_password_text, %{user: user, url: url})
end
def generate_email("update_email", user, %{"url" => url}) do
user
|> base_email(dgettext("emails", "Update your Memex email"))
|> render_body("update_email.html", %{user: user, url: url})
|> text_body(EmailView.render("update_email.txt", %{user: user, url: url}))
|> html_email(:update_email_html, %{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

View File

@ -9,6 +9,7 @@ defmodule Memex.Contexts.Context do
alias Ecto.{Changeset, UUID}
alias Memex.{Accounts.User, Repo}
@derive {Phoenix.Param, key: :slug}
@derive {Jason.Encoder,
only: [
:slug,

View File

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

View File

@ -8,6 +8,7 @@ defmodule Memex.Notes.Note do
alias Ecto.{Changeset, UUID}
alias Memex.{Accounts.User, Repo}
@derive {Phoenix.Param, key: :slug}
@derive {Jason.Encoder,
only: [
:slug,

View File

@ -8,6 +8,7 @@ defmodule Memex.Pipelines.Pipeline do
alias Ecto.{Changeset, UUID}
alias Memex.{Accounts.User, Pipelines.Steps.Step, Repo}
@derive {Phoenix.Param, key: :slug}
@derive {Jason.Encoder,
only: [
:slug,

View File

@ -1,54 +1,61 @@
defmodule MemexWeb do
@moduledoc """
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:
use MemexWeb, :controller
use MemexWeb, :view
use MemexWeb, :html
The definitions below will be executed for every view,
controller, etc, so keep them short and clean, focused
The definitions below will be executed for every controller,
component, etc, so keep them short and clean, focused
on imports, uses and aliases.
Do NOT define functions inside the quoted expressions
below. Instead, define any helper function in modules
and import those modules here.
below. Instead, define additional modules and import
those modules here.
"""
def controller do
quote do
use Phoenix.Controller, namespace: MemexWeb
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 MemexWeb.Gettext
alias MemexWeb.Endpoint
alias MemexWeb.Router.Helpers, as: Routes
import Phoenix.Controller
import Phoenix.LiveView.Router
end
end
def view do
def channel do
quote do
use Phoenix.View,
root: "lib/memex_web/templates",
namespace: MemexWeb
use Phoenix.Channel
end
end
# Import convenience functions from controllers
import Phoenix.Controller,
only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1]
def controller do
quote do
use Phoenix.Controller,
formats: [:html, :json],
layouts: [html: MemexWeb.Layouts]
# Include shared imports and aliases for views
unquote(view_helpers())
# credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
import Plug.Conn
import MemexWeb.Gettext
unquote(verified_routes())
end
end
def live_view do
quote do
use Phoenix.LiveView, layout: {MemexWeb.LayoutView, :live}
use Phoenix.LiveView,
layout: {MemexWeb.Layouts, :app}
on_mount MemexWeb.InitAssigns
unquote(view_helpers())
unquote(html_helpers())
end
end
@ -56,50 +63,46 @@ defmodule MemexWeb do
quote do
use Phoenix.LiveComponent
unquote(view_helpers())
unquote(html_helpers())
end
end
def component do
def html do
quote do
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
def router do
defp html_helpers 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 MemexWeb.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
use Phoenix.HTML
# Import LiveView and .heex helpers (live_render, link, <.form>, etc)
# Import basic rendering functionality (render, render_layout, etc)
import Phoenix.{Component, View}
import MemexWeb.{ErrorHelpers, Gettext, CoreComponents, ViewHelpers}
# credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
alias MemexWeb.Endpoint
alias MemexWeb.Router.Helpers, as: Routes
import Phoenix.Component
import MemexWeb.{ErrorHelpers, Gettext, CoreComponents, HTMLHelpers}
# Shortcut for generating JS commands
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: MemexWeb.Endpoint,
router: MemexWeb.Router,
statics: MemexWeb.static_paths()
end
end

View File

@ -88,11 +88,9 @@ defmodule MemexWeb.Components.ContextsTableComponent do
@spec get_value_for_key(atom(), Context.t(), additional_data :: map()) ::
any() | {any(), Rendered.t()}
defp get_value_for_key(:slug, %{slug: slug}, _additional_data) do
assigns = %{slug: slug}
defp get_value_for_key(:slug, %{slug: slug} = assigns, _additional_data) do
slug_block = ~H"""
<.link navigate={Routes.context_show_path(Endpoint, :show, @slug)} class="link">
<.link navigate={~p"/context/#{@slug}"} class="link">
<%= @slug %>
</.link>
"""
@ -100,16 +98,10 @@ defmodule MemexWeb.Components.ContextsTableComponent do
{slug, slug_block}
end
defp get_value_for_key(:tags, %{tags: tags}, _additional_data) do
assigns = %{tags: tags}
defp get_value_for_key(:tags, assigns, _additional_data) do
~H"""
<div class="flex flex-wrap justify-center space-x-1">
<.link
:for={tag <- @tags}
patch={Routes.context_index_path(Endpoint, :search, tag)}
class="link"
>
<.link :for={tag <- @tags} patch={~p"/contexts/#{tag}"} class="link">
<%= tag %>
</.link>
</div>

View File

@ -3,13 +3,13 @@ defmodule MemexWeb.CoreComponents do
Provides core UI components.
"""
use Phoenix.Component
import MemexWeb.{Gettext, ViewHelpers}
use MemexWeb, :verified_routes
import MemexWeb.{Gettext, HTMLHelpers}
alias Memex.{Accounts, Accounts.Invite, Accounts.User}
alias Memex.Contexts.Context
alias Memex.Notes.Note
alias Memex.Pipelines.Steps.Step
alias MemexWeb.{Endpoint, HomeLive}
alias MemexWeb.Router.Helpers, as: Routes
alias Phoenix.HTML
alias Phoenix.LiveView.JS
@ -31,13 +31,13 @@ defmodule MemexWeb.CoreComponents do
## Examples
<.modal return_to={Routes.<%= schema.singular %>_index_path(Endpoint, :index)}>
<.modal return_to={~p"/\#{<%= schema.plural %>}"}>
<.live_component
module={<%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.FormComponent}
id={@<%= schema.singular %>.id || :new}
title={@page_title}
action={@live_action}
return_to={Routes.<%= schema.singular %>_index_path(Endpoint, :index)}
return_to={~p"/\#{<%= schema.singular %>}"}
<%= schema.singular %>: @<%= schema.singular %>
/>
</.modal>
@ -167,7 +167,7 @@ defmodule MemexWeb.CoreComponents do
link =
HTML.Link.link(
"[[#{slug}]]",
to: Routes.note_show_path(Endpoint, :show, slug),
to: ~p"/note/#{slug}",
class: "link inline",
data: [qa: "#{data_qa_prefix}-#{slug}"]
)

View File

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

View File

@ -1,10 +1,7 @@
<nav role="navigation" class="mb-8 px-8 py-4 w-full bg-primary-900 text-primary-400">
<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">
<.link
navigate={Routes.live_path(Endpoint, HomeLive)}
class="mx-2 my-1 leading-5 text-xl text-primary-400 hover:underline"
>
<.link navigate={~p"/"} class="mx-2 my-1 leading-5 text-xl text-primary-400 hover:underline">
<%= gettext("memEx") %>
</.link>
@ -21,28 +18,19 @@
<ul class="flex flex-row flex-wrap justify-center items-center
text-lg text-primary-400 text-ellipsis">
<li class="mx-2 my-1">
<.link
navigate={Routes.note_index_path(Endpoint, :index)}
class="text-primary-400 hover:underline truncate"
>
<.link navigate={~p"/notes"} class="text-primary-400 hover:underline truncate">
<%= gettext("notes") %>
</.link>
</li>
<li class="mx-2 my-1">
<.link
navigate={Routes.context_index_path(Endpoint, :index)}
class="text-primary-400 hover:underline truncate"
>
<.link navigate={~p"/contexts"} class="text-primary-400 hover:underline truncate">
<%= gettext("contexts") %>
</.link>
</li>
<li class="mx-2 my-1">
<.link
navigate={Routes.pipeline_index_path(Endpoint, :index)}
class="text-primary-400 hover:underline truncate"
>
<.link navigate={~p"/pipelines"} class="text-primary-400 hover:underline truncate">
<%= gettext("pipelines") %>
</.link>
</li>
@ -51,25 +39,19 @@
<%= if @current_user do %>
<li :if={@current_user |> Accounts.is_already_admin?()} class="mx-2 my-1">
<.link
navigate={Routes.invite_index_path(Endpoint, :index)}
class="text-primary-400 hover:underline"
>
<.link navigate={~p"/invites"} class="text-primary-400 hover:underline">
<%= gettext("invites") %>
</.link>
</li>
<li class="mx-2 my-1">
<.link
navigate={Routes.user_settings_path(Endpoint, :edit)}
class="text-primary-400 hover:underline truncate"
>
<.link navigate={~p"/users/settings"} class="text-primary-400 hover:underline truncate">
<%= @current_user.email %>
</.link>
</li>
<li class="mx-2 my-1">
<.link
href={Routes.user_session_path(Endpoint, :delete)}
href={~p"/users/log_out"}
method="delete"
data-confirm={dgettext("prompts", "are you sure you want to log out?")}
aria-label={gettext("log out")}
@ -84,7 +66,7 @@
class="mx-2 my-1"
>
<.link
navigate={Routes.live_dashboard_path(Endpoint, :home)}
navigate={~p"/dashboard"}
class="text-primary-400 hover:underline"
aria-label={gettext("live dashboard")}
>
@ -93,19 +75,13 @@
</li>
<% else %>
<li :if={Accounts.allow_registration?()} class="mx-2 my-1">
<.link
href={Routes.user_registration_path(Endpoint, :new)}
class="text-primary-400 hover:underline truncate"
>
<.link href={~p"/users/register"} class="text-primary-400 hover:underline truncate">
<%= dgettext("actions", "register") %>
</.link>
</li>
<li class="mx-2 my-1">
<.link
href={Routes.user_session_path(Endpoint, :new)}
class="text-primary-400 hover:underline truncate"
>
<.link href={~p"/users/log_in"} class="text-primary-400 hover:underline truncate">
<%= dgettext("actions", "log in") %>
</.link>
</li>

View File

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

View File

@ -3,11 +3,11 @@
<.topbar current_user={assigns[:current_user]} />
<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">
<%= get_flash(@conn, :info) %>
<p :if={@flash["info"]} class="alert alert-info" role="alert">
<%= @flash["info"] %>
</p>
<p :if={get_flash(@conn, :error)} class="alert alert-danger" role="alert">
<%= get_flash(@conn, :error) %>
<p :if={@flash["error"]} class="alert alert-danger" role="alert">
<%= @flash["error"] %>
</p>
</div>
</header>

View File

@ -9,11 +9,8 @@
<hr style="margin: 2em auto; border-width: 1px; border-color: rgb(161, 161, 170); width: 100%; max-width: 42rem;" />
<a style="color: rgb(161, 161, 170);" href={Routes.live_url(Endpoint, HomeLive)}>
<%= dgettext(
"emails",
"This email was sent from memEx"
) %>
<a style="color: rgb(161, 161, 170);" href={~p"/"}>
<%= dgettext("emails", "This email was sent from memEx") %>
</a>
</body>
</html>

View File

@ -0,0 +1,9 @@
<%= @email.subject %>
====================
<%= @inner_content %>
=====================
<%= dgettext("emails", "This email was sent from memEx at %{url}", url: ~p"/") %>

View File

@ -8,13 +8,8 @@
<.live_title suffix={" | #{gettext("memEx")}"}>
<%= assigns[:page_title] || gettext("memEx") %>
</.live_title>
<link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/css/app.css")} />
<script
defer
phx-track-static
type="text/javascript"
src={Routes.static_path(@conn, "/js/app.js")}
>
<link phx-track-static rel="stylesheet" href={~p"/css/app.css"} />
<script defer phx-track-static type="text/javascript" src={~p"/js/app.js"}>
</script>
</head>

View File

@ -92,7 +92,7 @@ defmodule MemexWeb.Components.NotesTableComponent do
assigns = %{slug: slug}
slug_block = ~H"""
<.link navigate={Routes.note_show_path(Endpoint, :show, @slug)} class="link">
<.link navigate={~p"/note/#{@slug}"} class="link">
<%= @slug %>
</.link>
"""
@ -105,7 +105,7 @@ defmodule MemexWeb.Components.NotesTableComponent do
~H"""
<div class="flex flex-wrap justify-center space-x-1">
<.link :for={tag <- @tags} patch={Routes.note_index_path(Endpoint, :search, tag)} class="link">
<.link :for={tag <- @tags} patch={~p"/notes/#{tag}"} class="link">
<%= tag %>
</.link>
</div>

View File

@ -93,7 +93,7 @@ defmodule MemexWeb.Components.PipelinesTableComponent do
assigns = %{slug: slug}
slug_block = ~H"""
<.link navigate={Routes.pipeline_show_path(Endpoint, :show, @slug)} class="link">
<.link navigate={~p"/pipeline/#{@slug}"} class="link">
<%= @slug %>
</.link>
"""
@ -118,11 +118,7 @@ defmodule MemexWeb.Components.PipelinesTableComponent do
~H"""
<div class="flex flex-wrap justify-center space-x-1">
<.link
:for={tag <- @tags}
patch={Routes.pipeline_index_path(Endpoint, :search, tag)}
class="link"
>
<.link :for={tag <- @tags} patch={~p"/pipelines/#{tag}"} class="link">
<%= tag %>
</.link>
</div>

View File

@ -6,7 +6,8 @@ defmodule MemexWeb.EmailController do
use MemexWeb, :controller
alias Memex.Accounts.User
plug :put_layout, {MemexWeb.LayoutView, :email}
plug :put_root_layout, html: {MemexWeb.Layouts, :email_html}
plug :put_layout, false
@sample_assigns %{
email: %{subject: "Example subject"},
@ -18,6 +19,6 @@ defmodule MemexWeb.EmailController do
Debug route used to preview emails
"""
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

View File

@ -0,0 +1,9 @@
defmodule MemexWeb.EmailHTML do
@moduledoc """
Renders email templates
"""
use MemexWeb, :html
embed_templates "email_html/*"
end

View File

@ -15,7 +15,7 @@
<br />
<a style="margin: 1em; color: rgb(31, 31, 31);" href={@url}><%= @url %></a>
<a style="margin: 1em; color: rgb(161, 161, 170);" href={@url}><%= @url %></a>
<br />

View File

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

View File

@ -9,7 +9,7 @@
<br />
<a style="margin: 1em; color: rgb(31, 31, 31);" href={@url}><%= @url %></a>
<a style="margin: 1em; color: rgb(161, 161, 170);" href={@url}><%= @url %></a>
<br />

View File

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

View File

@ -9,7 +9,7 @@
<br />
<a style="margin: 1em; color: rgb(31, 31, 31);" href={@url}><%= @url %></a>
<a style="margin: 1em; color: rgb(161, 161, 170);" href={@url}><%= @url %></a>
<br />

View File

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

View File

@ -1,15 +1,16 @@
defmodule MemexWeb.ErrorView do
use MemexWeb, :view
alias MemexWeb.HomeLive
defmodule MemexWeb.ErrorHTML do
use MemexWeb, :html
def template_not_found(error_path, _assigns) do
embed_templates "error_html/*"
def render(template, _assigns) do
error_string =
case error_path do
case template 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})
error(%{error_string: error_string})
end
end

View File

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

View File

@ -0,0 +1,14 @@
defmodule MemexWeb.ErrorJSON do
import MemexWeb.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 MemexWeb.HomeController do
@moduledoc """
Controller for home page
"""
use MemexWeb, :controller
def index(conn, _params) do
render(conn, "index.html")
end
end

View File

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

View File

@ -3,12 +3,11 @@ defmodule MemexWeb.UserAuth do
Functions for user session and authentication
"""
use MemexWeb, :verified_routes
import Plug.Conn
import Phoenix.Controller
import MemexWeb.Gettext
alias Memex.{Accounts, Accounts.User}
alias MemexWeb.HomeLive
alias MemexWeb.Router.Helpers, as: Routes
# Make the remember me cookie valid for 60 days.
# If you want bump or reduce this value, also change
@ -39,7 +38,7 @@ defmodule MemexWeb.UserAuth do
dgettext("errors", "You must confirm your account and log in to access this page.")
)
|> maybe_store_return_to()
|> redirect(to: Routes.user_session_path(conn, :new))
|> redirect(to: ~p"/users/log_in")
|> halt()
end
@ -49,8 +48,7 @@ defmodule MemexWeb.UserAuth do
conn
|> renew_session()
|> put_session(:user_token, token)
|> put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}")
|> put_token_in_session(token)
|> maybe_write_remember_me_cookie(token, params)
|> redirect(to: user_return_to || signed_in_path(conn))
end
@ -96,7 +94,7 @@ defmodule MemexWeb.UserAuth do
"""
def log_out_user(conn) do
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
MemexWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{})
@ -105,7 +103,7 @@ defmodule MemexWeb.UserAuth do
conn
|> renew_session()
|> delete_resp_cookie(@remember_me_cookie)
|> redirect(to: "/")
|> redirect(to: ~p"/")
end
@doc """
@ -119,19 +117,110 @@ defmodule MemexWeb.UserAuth do
end
defp ensure_user_token(conn) do
if user_token = get_session(conn, :user_token) do
{user_token, conn}
if token = get_session(conn, :user_token) do
{token, conn}
else
conn = fetch_cookies(conn, signed: [@remember_me_cookie])
if user_token = conn.cookies[@remember_me_cookie] do
{user_token, put_session(conn, :user_token, user_token)}
if token = conn.cookies[@remember_me_cookie] do
{token, put_token_in_session(conn, token)}
else
{nil, conn}
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 MemexWeb.PageLive do
use MemexWeb, :live_view
on_mount {MemexWeb.UserAuth, :mount_current_user}
...
end
Or use the `live_session` of your router to invoke the on_mount callback:
live_session :authenticated, on_mount: [{MemexWeb.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 """
Used for routes that require the user to not be authenticated.
"""
@ -161,7 +250,7 @@ defmodule MemexWeb.UserAuth do
dgettext("errors", "You must confirm your account and log in to access this page.")
)
|> maybe_store_return_to()
|> redirect(to: Routes.user_session_path(conn, :new))
|> redirect(to: ~p"/users/log_in")
|> halt()
end
end
@ -176,16 +265,34 @@ defmodule MemexWeb.UserAuth do
conn
|> put_flash(:error, dgettext("errors", "You are not authorized to view this page."))
|> maybe_store_return_to()
|> redirect(to: Routes.live_path(conn, HomeLive))
|> redirect(to: ~p"/")
|> halt()
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
put_session(conn, :user_return_to, current_path(conn))
end
defp maybe_store_return_to(conn), do: conn
defp signed_in_path(_conn), do: "/"
defp signed_in_path(_conn), do: ~p"/"
end

View File

@ -5,14 +5,14 @@ defmodule MemexWeb.UserConfirmationController do
alias Memex.Accounts
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
def create(conn, %{"user" => %{"email" => email}}) do
if user = Accounts.get_user_by_email(email) do
Accounts.deliver_user_confirmation_instructions(
user,
&Routes.user_confirmation_url(conn, :confirm, &1)
fn token -> url(MemexWeb.Endpoint, ~p"/users/confirm/#{token}") end
)
end

View File

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

View File

@ -7,7 +7,7 @@
:let={f}
for={%{}}
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"
>
<%= label(f, :email, gettext("Email"), class: "title text-lg text-primary-400") %>
@ -21,14 +21,10 @@
<hr class="hr" />
<div class="flex flex-row justify-center items-center space-x-4">
<.link
:if={Accounts.allow_registration?()}
href={Routes.user_registration_path(@conn, :new)}
class="btn btn-primary"
>
<.link :if={Accounts.allow_registration?()} href={~p"/users/register"} class="btn btn-primary">
<%= dgettext("actions", "register") %>
</.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") %>
</.link>
</div>

View File

@ -2,7 +2,6 @@ defmodule MemexWeb.UserRegistrationController do
use MemexWeb, :controller
import MemexWeb.Gettext
alias Memex.{Accounts, Accounts.Invites}
alias MemexWeb.HomeLive
def new(conn, %{"invite" => invite_token}) do
if Invites.valid_invite_token?(invite_token) do
@ -10,7 +9,7 @@ defmodule MemexWeb.UserRegistrationController do
else
conn
|> 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
@ -20,13 +19,13 @@ defmodule MemexWeb.UserRegistrationController do
else
conn
|> put_flash(:error, dgettext("errors", "Sorry, public registration is disabled"))
|> redirect(to: Routes.live_path(Endpoint, HomeLive))
|> redirect(to: ~p"/")
end
end
# renders new user registration page
defp render_new(conn, invite_token \\ nil) do
render(conn, "new.html",
render(conn, :new,
changeset: Accounts.change_user_registration(),
invite_token: invite_token,
page_title: gettext("register")
@ -39,7 +38,7 @@ defmodule MemexWeb.UserRegistrationController do
else
conn
|> 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
@ -49,7 +48,7 @@ defmodule MemexWeb.UserRegistrationController do
else
conn
|> put_flash(:error, dgettext("errors", "Sorry, public registration is disabled"))
|> redirect(to: Routes.live_path(Endpoint, HomeLive))
|> redirect(to: ~p"/")
end
end
@ -58,17 +57,17 @@ defmodule MemexWeb.UserRegistrationController do
{:ok, user} ->
Accounts.deliver_user_confirmation_instructions(
user,
&Routes.user_confirmation_url(conn, :confirm, &1)
fn token -> url(MemexWeb.Endpoint, ~p"/users/confirm/#{token}") end
)
conn
|> 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} ->
conn
|> put_flash(:error, dgettext("errors", "sorry, this invite was not found or expired"))
|> redirect(to: Routes.live_path(Endpoint, HomeLive))
|> redirect(to: ~p"/")
{:error, %Ecto.Changeset{} = changeset} ->
conn |> render("new.html", changeset: changeset, invite_token: invite_token)

View File

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

View File

@ -6,7 +6,7 @@
<.form
:let={f}
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"
>
<p :if={@changeset.action && not @changeset.valid?()} class="alert alert-danger col-span-3">
@ -40,10 +40,10 @@
<hr class="hr" />
<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") %>
</.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?") %>
</.link>
</div>

View File

@ -6,14 +6,14 @@ defmodule MemexWeb.UserResetPasswordController do
plug :get_user_by_reset_password_token when action in [:edit, :update]
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
def create(conn, %{"user" => %{"email" => email}}) do
if user = Accounts.get_user_by_email(email) do
Accounts.deliver_user_reset_password_instructions(
user,
&Routes.user_reset_password_url(conn, :edit, &1)
fn token -> url(MemexWeb.Endpoint, ~p"/users/reset_password/#{token}") end
)
end
@ -31,7 +31,7 @@ defmodule MemexWeb.UserResetPasswordController do
end
def edit(conn, _params) do
render(conn, "edit.html",
render(conn, :edit,
changeset: Accounts.change_user_password(conn.assigns.user),
page_title: gettext("Reset your password")
)
@ -44,10 +44,10 @@ defmodule MemexWeb.UserResetPasswordController do
{:ok, _} ->
conn
|> put_flash(:info, dgettext("prompts", "Password reset successfully."))
|> redirect(to: Routes.user_session_path(conn, :new))
|> redirect(to: ~p"/users/log_in")
{:error, changeset} ->
render(conn, "edit.html", changeset: changeset)
render(conn, :edit, changeset: changeset)
end
end