diff --git a/.formatter.exs b/.formatter.exs index bafd8f8..f5dc12d 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -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] ] diff --git a/lib/memex/accounts.ex b/lib/memex/accounts.ex index 56bcf7b..e9a9e14 100644 --- a/lib/memex/accounts.ex +++ b/lib/memex/accounts.ex @@ -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 diff --git a/lib/memex/accounts/email.ex b/lib/memex/accounts/email.ex index 3bd4cdd..810659f 100644 --- a/lib/memex/accounts/email.ex +++ b/lib/memex/accounts/email.ex @@ -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 diff --git a/lib/memex/contexts/context.ex b/lib/memex/contexts/context.ex index 69e470e..3aa3e96 100644 --- a/lib/memex/contexts/context.ex +++ b/lib/memex/contexts/context.ex @@ -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, diff --git a/lib/memex/logger.ex b/lib/memex/logger.ex index 098bdfc..d9d9002 100644 --- a/lib/memex/logger.ex +++ b/lib/memex/logger.ex @@ -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 diff --git a/lib/memex/notes/note.ex b/lib/memex/notes/note.ex index 2c8bb3c..d52b8ea 100644 --- a/lib/memex/notes/note.ex +++ b/lib/memex/notes/note.ex @@ -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, diff --git a/lib/memex/pipelines/pipeline.ex b/lib/memex/pipelines/pipeline.ex index 75b9676..15d1be4 100644 --- a/lib/memex/pipelines/pipeline.ex +++ b/lib/memex/pipelines/pipeline.ex @@ -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, diff --git a/lib/memex_web.ex b/lib/memex_web.ex index 9b5a8c1..6a2b5e2 100644 --- a/lib/memex_web.ex +++ b/lib/memex_web.ex @@ -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 diff --git a/lib/memex_web/components/contexts_table_component.ex b/lib/memex_web/components/contexts_table_component.ex index 48b69ef..cdc1fce 100644 --- a/lib/memex_web/components/contexts_table_component.ex +++ b/lib/memex_web/components/contexts_table_component.ex @@ -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 %> """ @@ -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"""