add swoosh and oban
This commit is contained in:
parent
acf64cee18
commit
a39c3da351
@ -107,3 +107,11 @@ In `prod` mode (or in the Docker container), Cannery will listen for the same en
|
|||||||
|
|
||||||
- `SECRET_KEY_BASE`: Secret key base used to sign cookies. Must be generated
|
- `SECRET_KEY_BASE`: Secret key base used to sign cookies. Must be generated
|
||||||
with `docker run -it shibaobun/cannery mix phx.gen.secret` and set for server to start.
|
with `docker run -it shibaobun/cannery mix phx.gen.secret` and set for server to start.
|
||||||
|
- `SMTP_HOST`: The url for your SMTP email provider. Must be set
|
||||||
|
- `SMTP_PORT`: The port for your SMTP relay. Defaults to `587`.
|
||||||
|
- `SMTP_USERNAME`: The username for your SMTP relay. Must be set!
|
||||||
|
- `SMTP_PASSWORD`: The password for your SMTP relay. Must be set!
|
||||||
|
- `SMTP_SSL`: Set to `true` to enable SSL for emails. Defaults to `false`.
|
||||||
|
- `EMAIL_FROM`: Sets the sender email in sent emails. Defaults to
|
||||||
|
`no-reply@HOST` where `HOST` was previously defined.
|
||||||
|
- `EMAIL_NAME`: Sets the sender name in sent emails. Defaults to "Cannery".
|
||||||
|
@ -59,6 +59,14 @@ You can use the following environment variables to configure Cannery in
|
|||||||
- `REGISTRATION`: Controls if user sign-up should be invite only or set to
|
- `REGISTRATION`: Controls if user sign-up should be invite only or set to
|
||||||
public. Set to `public` to enable public registration. Defaults to `invite`.
|
public. Set to `public` to enable public registration. Defaults to `invite`.
|
||||||
- `LOCALE`: Sets a custom locale. Defaults to `en_US`.
|
- `LOCALE`: Sets a custom locale. Defaults to `en_US`.
|
||||||
|
- `SMTP_HOST`: The url for your SMTP email provider. Must be set
|
||||||
|
- `SMTP_PORT`: The port for your SMTP relay. Defaults to `587`.
|
||||||
|
- `SMTP_USERNAME`: The username for your SMTP relay. Must be set!
|
||||||
|
- `SMTP_PASSWORD`: The password for your SMTP relay. Must be set!
|
||||||
|
- `SMTP_SSL`: Set to `true` to enable SSL for emails. Defaults to `false`.
|
||||||
|
- `EMAIL_FROM`: Sets the sender email in sent emails. Defaults to
|
||||||
|
`no-reply@HOST` where `HOST` was previously defined.
|
||||||
|
- `EMAIL_NAME`: Sets the sender name in sent emails. Defaults to "Cannery".
|
||||||
|
|
||||||
# Contribution
|
# Contribution
|
||||||
|
|
||||||
|
@ -43,6 +43,12 @@ config :swoosh, :api_client, false
|
|||||||
# Gettext
|
# Gettext
|
||||||
config :gettext, :default_locale, "en_US"
|
config :gettext, :default_locale, "en_US"
|
||||||
|
|
||||||
|
# Configure Oban
|
||||||
|
config :cannery, Oban,
|
||||||
|
repo: Cannery.Repo,
|
||||||
|
plugins: [Oban.Plugins.Pruner],
|
||||||
|
queues: [default: 10, mailers: 20]
|
||||||
|
|
||||||
# Configure esbuild (the version is required)
|
# Configure esbuild (the version is required)
|
||||||
# config :esbuild,
|
# config :esbuild,
|
||||||
# version: "0.14.0",
|
# version: "0.14.0",
|
||||||
|
@ -26,7 +26,9 @@ database_url =
|
|||||||
"ecto://postgres:postgres@cannery-db/cannery"
|
"ecto://postgres:postgres@cannery-db/cannery"
|
||||||
end
|
end
|
||||||
|
|
||||||
host = System.get_env("HOST") || "localhost"
|
host =
|
||||||
|
System.get_env("HOST") ||
|
||||||
|
raise "No hostname set! Must be the domain and tld like `cannery.bubbletea.dev`."
|
||||||
|
|
||||||
interface =
|
interface =
|
||||||
if config_env() in [:dev, :test],
|
if config_env() in [:dev, :test],
|
||||||
@ -65,6 +67,17 @@ if config_env() == :prod do
|
|||||||
|
|
||||||
config :cannery, CanneryWeb.Endpoint, secret_key_base: secret_key_base
|
config :cannery, CanneryWeb.Endpoint, secret_key_base: secret_key_base
|
||||||
|
|
||||||
|
# Set up SMTP settings
|
||||||
|
config :cannery, Cannery.Mailer,
|
||||||
|
adapter: Swoosh.Adapters.SMTP,
|
||||||
|
relay: System.get_env("SMTP_HOST") || raise("No SMTP_HOST set!"),
|
||||||
|
port: System.get_env("SMTP_PORT") || 587,
|
||||||
|
username: System.get_env("SMTP_USERNAME") || raise("No SMTP_USERNAME set!"),
|
||||||
|
password: System.get_env("SMTP_PASSWORD") || raise("No SMTP_PASSWORD set!"),
|
||||||
|
ssl: System.get_env("SMTP_SSL") == "true",
|
||||||
|
email_from: System.get_env("EMAIL_FROM") || "no-reply@#{System.get_env("HOST")}",
|
||||||
|
email_name: System.get_env("EMAIL_NAME") || "Cannery"
|
||||||
|
|
||||||
# ## Using releases
|
# ## Using releases
|
||||||
#
|
#
|
||||||
# If you are doing OTP releases, you need to instruct Phoenix
|
# If you are doing OTP releases, you need to instruct Phoenix
|
||||||
|
@ -27,3 +27,6 @@ config :logger, level: :warn
|
|||||||
|
|
||||||
# Initialize plugs at runtime for faster test compilation
|
# Initialize plugs at runtime for faster test compilation
|
||||||
config :phoenix, :plug_init_mode, :runtime
|
config :phoenix, :plug_init_mode, :runtime
|
||||||
|
|
||||||
|
# Disable Oban
|
||||||
|
config :cannery, Oban, queues: false, plugins: false
|
||||||
|
@ -10,8 +10,15 @@ services:
|
|||||||
- DATABASE_URL="ecto://postgres:postgres@cannery-db/cannery"
|
- DATABASE_URL="ecto://postgres:postgres@cannery-db/cannery"
|
||||||
# Use `docker run -it shibaobun/cannery mix phx.gen.secret` to generate a secret key base
|
# Use `docker run -it shibaobun/cannery mix phx.gen.secret` to generate a secret key base
|
||||||
- SECRET_KEY_BASE="change-me-this-is-really-important-seriously-change-it"
|
- SECRET_KEY_BASE="change-me-this-is-really-important-seriously-change-it"
|
||||||
# uncomment to enable sign ups, watch for spam!
|
# uncomment to enable public sign ups, not recommended
|
||||||
# - REGISTRATION="public"
|
# - REGISTRATION="public"
|
||||||
|
# - SMTP_HOST="cannery.example.tld" # must be set!
|
||||||
|
# - SMTP_PORT="587" # optional
|
||||||
|
# - SMTP_USERNAME="username"
|
||||||
|
# - SMTP_PASSWORD="password"
|
||||||
|
# - SMTP_SSL="false" # optional
|
||||||
|
# - EMAIL_FROM="no-reply@cannery.example.tld" # optional
|
||||||
|
# - EMAIL_NAME="Cannery" # optional
|
||||||
expose:
|
expose:
|
||||||
- "4000"
|
- "4000"
|
||||||
depends_on:
|
depends_on:
|
||||||
|
@ -5,7 +5,8 @@ defmodule Cannery.Accounts do
|
|||||||
|
|
||||||
import Ecto.Query, warn: false
|
import Ecto.Query, warn: false
|
||||||
alias Cannery.Repo
|
alias Cannery.Repo
|
||||||
alias Cannery.Accounts.{User, UserNotifier, UserToken}
|
alias Cannery.Accounts.{User, UserToken}
|
||||||
|
alias Cannery.Mailer
|
||||||
alias Ecto.{Changeset, Multi}
|
alias Ecto.{Changeset, Multi}
|
||||||
|
|
||||||
## Database getters
|
## Database getters
|
||||||
@ -208,7 +209,7 @@ defmodule Cannery.Accounts do
|
|||||||
{encoded_token, user_token} = UserToken.build_email_token(user, "change:#{current_email}")
|
{encoded_token, user_token} = UserToken.build_email_token(user, "change:#{current_email}")
|
||||||
|
|
||||||
Repo.insert!(user_token)
|
Repo.insert!(user_token)
|
||||||
UserNotifier.deliver_update_email_instructions(user, update_email_url_fun.(encoded_token))
|
Mailer.deliver_update_email_instructions(user, update_email_url_fun.(encoded_token))
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
@ -319,7 +320,7 @@ defmodule Cannery.Accounts do
|
|||||||
else
|
else
|
||||||
{encoded_token, user_token} = UserToken.build_email_token(user, "confirm")
|
{encoded_token, user_token} = UserToken.build_email_token(user, "confirm")
|
||||||
Repo.insert!(user_token)
|
Repo.insert!(user_token)
|
||||||
UserNotifier.deliver_confirmation_instructions(user, confirmation_url_fun.(encoded_token))
|
Mailer.deliver_confirmation_instructions(user, confirmation_url_fun.(encoded_token))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -364,7 +365,7 @@ defmodule Cannery.Accounts do
|
|||||||
when is_function(reset_password_url_fun, 1) do
|
when is_function(reset_password_url_fun, 1) do
|
||||||
{encoded_token, user_token} = UserToken.build_email_token(user, "reset_password")
|
{encoded_token, user_token} = UserToken.build_email_token(user, "reset_password")
|
||||||
Repo.insert!(user_token)
|
Repo.insert!(user_token)
|
||||||
UserNotifier.deliver_reset_password_instructions(user, reset_password_url_fun.(encoded_token))
|
Mailer.deliver_reset_password_instructions(user, reset_password_url_fun.(encoded_token))
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
54
lib/cannery/accounts/email.ex
Normal file
54
lib/cannery/accounts/email.ex
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
defmodule Cannery.Email do
|
||||||
|
@moduledoc """
|
||||||
|
Emails that can be sent using Swoosh.
|
||||||
|
|
||||||
|
You can find the base email templates at
|
||||||
|
`lib/cannery_web/templates/layout/email.html.heex` for html emails and
|
||||||
|
`lib/cannery_web/templates/layout/email.txt.heex` for text emails.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Phoenix.Swoosh, view: Cannery.EmailView, layout: {Cannery.LayoutView, :email}
|
||||||
|
import CanneryWeb.Gettext
|
||||||
|
alias Cannery.Accounts.User
|
||||||
|
alias CanneryWeb.EmailView
|
||||||
|
|
||||||
|
@typedoc """
|
||||||
|
Represents an HTML and text body email that can be sent
|
||||||
|
"""
|
||||||
|
@type t() :: Swoosh.Email.t()
|
||||||
|
|
||||||
|
@spec base_email(User.t(), String.t()) :: t()
|
||||||
|
defp base_email(%User{email: email}, subject) do
|
||||||
|
new()
|
||||||
|
|> to(email)
|
||||||
|
|> from({
|
||||||
|
Application.get_env(:cannery, Cannery.Mailer)[:email_name],
|
||||||
|
Application.get_env(:cannery, Cannery.Mailer)[:email_from]
|
||||||
|
})
|
||||||
|
|> subject(subject)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec welcome_email(User.t(), String.t()) :: t()
|
||||||
|
def welcome_email(user, url) do
|
||||||
|
user
|
||||||
|
|> base_email(dgettext("emails", "Confirm your %{name} account", name: "Cannery"))
|
||||||
|
|> render_body("confirm_email.html", %{user: user, url: url})
|
||||||
|
|> text_body(EmailView.render("confirm_email.txt", %{user: user, url: url}))
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec reset_password_email(User.t(), String.t()) :: t()
|
||||||
|
def reset_password_email(user, url) do
|
||||||
|
user
|
||||||
|
|> base_email(dgettext("emails", "Reset your %{name} password", name: "Cannery"))
|
||||||
|
|> render_body("reset_password.html", %{user: user, url: url})
|
||||||
|
|> text_body(EmailView.render("reset_password.txt", %{user: user, url: url}))
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec update_email(User.t(), String.t()) :: t()
|
||||||
|
def update_email(user, url) do
|
||||||
|
user
|
||||||
|
|> base_email(dgettext("emails", "Update your %{name} email", name: "Cannery"))
|
||||||
|
|> render_body("update_email.html", %{user: user, url: url})
|
||||||
|
|> text_body(EmailView.render("update_email.txt", %{user: user, url: url}))
|
||||||
|
end
|
||||||
|
end
|
9
lib/cannery/accounts/email_worker.ex
Normal file
9
lib/cannery/accounts/email_worker.ex
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
defmodule Cannery.EmailWorker do
|
||||||
|
use Oban.Worker, queue: :mailers
|
||||||
|
alias Cannery.Mailer
|
||||||
|
|
||||||
|
@impl Oban.Worker
|
||||||
|
def perform(%Oban.Job{args: email}) do
|
||||||
|
email |> Mailer.deliver()
|
||||||
|
end
|
||||||
|
end
|
@ -1,77 +0,0 @@
|
|||||||
defmodule Cannery.Accounts.UserNotifier do
|
|
||||||
@moduledoc """
|
|
||||||
Contains all user emails and notifications
|
|
||||||
"""
|
|
||||||
|
|
||||||
# For simplicity, this module simply logs messages to the terminal.
|
|
||||||
# You should replace it by a proper email or notification tool, such as:
|
|
||||||
#
|
|
||||||
# * Swoosh - https://hexdocs.pm/swoosh
|
|
||||||
# * Bamboo - https://hexdocs.pm/bamboo
|
|
||||||
#
|
|
||||||
defp deliver(to, body) do
|
|
||||||
require Logger
|
|
||||||
Logger.debug(body)
|
|
||||||
{:ok, %{to: to, body: body}}
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Deliver instructions to confirm account.
|
|
||||||
"""
|
|
||||||
def deliver_confirmation_instructions(user, url) do
|
|
||||||
deliver(user.email, """
|
|
||||||
|
|
||||||
==============================
|
|
||||||
|
|
||||||
Hi #{user.email},
|
|
||||||
|
|
||||||
You can confirm your account by visiting the URL below:
|
|
||||||
|
|
||||||
#{url}
|
|
||||||
|
|
||||||
If you didn't create an account with us, please ignore this.
|
|
||||||
|
|
||||||
==============================
|
|
||||||
""")
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Deliver instructions to reset a user password.
|
|
||||||
"""
|
|
||||||
def deliver_reset_password_instructions(user, url) do
|
|
||||||
deliver(user.email, """
|
|
||||||
|
|
||||||
==============================
|
|
||||||
|
|
||||||
Hi #{user.email},
|
|
||||||
|
|
||||||
You can reset your password by visiting the URL below:
|
|
||||||
|
|
||||||
#{url}
|
|
||||||
|
|
||||||
If you didn't request this change, please ignore this.
|
|
||||||
|
|
||||||
==============================
|
|
||||||
""")
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Deliver instructions to update a user email.
|
|
||||||
"""
|
|
||||||
def deliver_update_email_instructions(user, url) do
|
|
||||||
deliver(user.email, """
|
|
||||||
|
|
||||||
==============================
|
|
||||||
|
|
||||||
Hi #{user.email},
|
|
||||||
|
|
||||||
You can change your email by visiting the URL below:
|
|
||||||
|
|
||||||
#{url}
|
|
||||||
|
|
||||||
If you didn't request this change, please ignore this.
|
|
||||||
|
|
||||||
==============================
|
|
||||||
""")
|
|
||||||
end
|
|
||||||
end
|
|
@ -15,7 +15,9 @@ defmodule Cannery.Application do
|
|||||||
# Start the PubSub system
|
# Start the PubSub system
|
||||||
{Phoenix.PubSub, name: Cannery.PubSub},
|
{Phoenix.PubSub, name: Cannery.PubSub},
|
||||||
# Start the Endpoint (http/https)
|
# Start the Endpoint (http/https)
|
||||||
CanneryWeb.Endpoint
|
CanneryWeb.Endpoint,
|
||||||
|
# Add Oban
|
||||||
|
{Oban, oban_config()}
|
||||||
# Start a worker by calling: Cannery.Worker.start_link(arg)
|
# Start a worker by calling: Cannery.Worker.start_link(arg)
|
||||||
# {Cannery.Worker, arg}
|
# {Cannery.Worker, arg}
|
||||||
]
|
]
|
||||||
@ -39,4 +41,8 @@ defmodule Cannery.Application do
|
|||||||
CanneryWeb.Endpoint.config_change(changed, removed)
|
CanneryWeb.Endpoint.config_change(changed, removed)
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp oban_config() do
|
||||||
|
Application.fetch_env!(:cannery, Oban)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -4,4 +4,30 @@ defmodule Cannery.Mailer do
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
use Swoosh.Mailer, otp_app: :cannery
|
use Swoosh.Mailer, otp_app: :cannery
|
||||||
|
alias Cannery.{Accounts.User, Email, EmailWorker}
|
||||||
|
alias Oban.Job
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Deliver instructions to confirm account.
|
||||||
|
"""
|
||||||
|
@spec deliver_confirmation_instructions(User.t(), String.t()) :: {:ok, Job.t()}
|
||||||
|
def deliver_confirmation_instructions(user, url) do
|
||||||
|
{:ok, Email.welcome_email(user, url) |> EmailWorker.new() |> Oban.insert!()}
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Deliver instructions to reset a user password.
|
||||||
|
"""
|
||||||
|
@spec deliver_reset_password_instructions(User.t(), String.t()) :: {:ok, Job.t()}
|
||||||
|
def deliver_reset_password_instructions(user, url) do
|
||||||
|
{:ok, Email.reset_password_email(user, url) |> EmailWorker.new() |> Oban.insert!()}
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Deliver instructions to update a user email.
|
||||||
|
"""
|
||||||
|
@spec deliver_update_email_instructions(User.t(), String.t()) :: {:ok, Job.t()}
|
||||||
|
def deliver_update_email_instructions(user, url) do
|
||||||
|
{:ok, Email.update_email(user, url) |> EmailWorker.new() |> Oban.insert!()}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
12
lib/cannery_web/templates/email/confirm_email.html.eex
Normal file
12
lib/cannery_web/templates/email/confirm_email.html.eex
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
<%= dgettext("emails", "Hi %{email},", email: @user.email) %>
|
||||||
|
|
||||||
|
<%= dgettext("emails", "Welcome to %{name}!", name: "Cannery") %>
|
||||||
|
|
||||||
|
<%= dgettext("emails", "You can confirm your account by visiting the URL below:") %>
|
||||||
|
|
||||||
|
<%= @url %>
|
||||||
|
|
||||||
|
<%= dgettext("emails",
|
||||||
|
"If you didn't create an account at %{url}, please ignore this.",
|
||||||
|
url: Routes.live_url(Endpoint, HomeLive)) %>
|
12
lib/cannery_web/templates/email/confirm_email.txt.eex
Normal file
12
lib/cannery_web/templates/email/confirm_email.txt.eex
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
<%= dgettext("emails", "Hi %{email},", email: @user.email) %>
|
||||||
|
|
||||||
|
<%= dgettext("emails", "Welcome to %{name}%!", name: "Cannery") %>
|
||||||
|
|
||||||
|
<%= dgettext("emails", "You can confirm your account by visiting the URL below:") %>
|
||||||
|
|
||||||
|
<%= @url %>
|
||||||
|
|
||||||
|
<%= dgettext("emails",
|
||||||
|
"If you didn't create an account at %{url}, please ignore this.",
|
||||||
|
url: Routes.live_url(Endpoint, HomeLive)) %>
|
10
lib/cannery_web/templates/email/reset_password.html.eex
Normal file
10
lib/cannery_web/templates/email/reset_password.html.eex
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
<%= dgettext("emails", "Hi %{email},", email: @user.email) %>
|
||||||
|
|
||||||
|
<%= dgettext("emails", "You can reset your password by visiting the URL below:") %>
|
||||||
|
|
||||||
|
<%= @url %>
|
||||||
|
|
||||||
|
<%= dgettext("emails",
|
||||||
|
"If you didn't request this change from %{url}, please ignore this.",
|
||||||
|
url: Routes.live_url(Endpoint, HomeLive)) %>
|
10
lib/cannery_web/templates/email/reset_password.txt.eex
Normal file
10
lib/cannery_web/templates/email/reset_password.txt.eex
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
<%= dgettext("emails", "Hi %{email},", email: @user.email) %>
|
||||||
|
|
||||||
|
<%= dgettext("emails", "You can reset your password by visiting the URL below:") %>
|
||||||
|
|
||||||
|
<%= @url %>
|
||||||
|
|
||||||
|
<%= dgettext("emails",
|
||||||
|
"If you didn't request this change from %{url}, please ignore this.",
|
||||||
|
url: Routes.live_url(Endpoint, HomeLive)) %>
|
10
lib/cannery_web/templates/email/update_email.html.eex
Normal file
10
lib/cannery_web/templates/email/update_email.html.eex
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
<%= dgettext("emails", "Hi %{email},", email: @user.email) %>
|
||||||
|
|
||||||
|
<%= dgettext("emails", "You can change your email by visiting the URL below:") %>
|
||||||
|
|
||||||
|
<%= @url %>
|
||||||
|
|
||||||
|
<%= dgettext("emails",
|
||||||
|
"If you didn't request this change from %{url}, please ignore this.",
|
||||||
|
url: Routes.live_url(Endpoint, HomeLive)) %>
|
10
lib/cannery_web/templates/email/update_email.txt.eex
Normal file
10
lib/cannery_web/templates/email/update_email.txt.eex
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
<%= dgettext("emails", "Hi %{email},", email: @user.email) %>
|
||||||
|
|
||||||
|
<%= dgettext("emails", "You can change your email by visiting the URL below:") %>
|
||||||
|
|
||||||
|
<%= @url %>
|
||||||
|
|
||||||
|
<%= dgettext("emails",
|
||||||
|
"If you didn't request this change from %{url}, please ignore this.",
|
||||||
|
url: Routes.live_url(Endpoint, HomeLive)) %>
|
16
lib/cannery_web/templates/layout/email.html.heex
Normal file
16
lib/cannery_web/templates/layout/email.html.heex
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
<%= @email.subject %>
|
||||||
|
</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<%= @inner_content %>
|
||||||
|
|
||||||
|
<hr style="border-width: 1px; border-color: rgb(212, 212, 216); width: 100%; max-width: 42rem;">
|
||||||
|
|
||||||
|
<a href={Routes.live_url(Endpoint, HomeLive)}>
|
||||||
|
<%= dgettext("emails", "This email was sent from %{name}", name: "Cannery") %>
|
||||||
|
</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
13
lib/cannery_web/templates/layout/email.txt.eex
Normal file
13
lib/cannery_web/templates/layout/email.txt.eex
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<%= @email.subject %>
|
||||||
|
|
||||||
|
|
||||||
|
====================
|
||||||
|
|
||||||
|
<%= @inner_content %>
|
||||||
|
|
||||||
|
=====================
|
||||||
|
|
||||||
|
<%= dgettext("emails",
|
||||||
|
"This email was sent from %{name} at %{url}",
|
||||||
|
name: "Cannery",
|
||||||
|
url: Routes.live_url(Endpoint, HomeLive)) %>
|
8
lib/cannery_web/views/email_view.ex
Normal file
8
lib/cannery_web/views/email_view.ex
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
defmodule CanneryWeb.EmailView do
|
||||||
|
@moduledoc """
|
||||||
|
A view for email-related helper functions
|
||||||
|
"""
|
||||||
|
alias CanneryWeb.{Endpoint, HomeLive}
|
||||||
|
|
||||||
|
use CanneryWeb, :view
|
||||||
|
end
|
2
mix.exs
2
mix.exs
@ -46,6 +46,8 @@ defmodule Cannery.MixProject do
|
|||||||
{:phoenix_live_dashboard, "~> 0.6"},
|
{:phoenix_live_dashboard, "~> 0.6"},
|
||||||
# {:esbuild, "~> 0.3", runtime: Mix.env() == :dev},
|
# {:esbuild, "~> 0.3", runtime: Mix.env() == :dev},
|
||||||
{:swoosh, "~> 1.3"},
|
{:swoosh, "~> 1.3"},
|
||||||
|
{:phoenix_swoosh, "~> 1.0"},
|
||||||
|
{:oban, "~> 2.10"},
|
||||||
{:telemetry_metrics, "~> 0.6"},
|
{:telemetry_metrics, "~> 0.6"},
|
||||||
{:telemetry_poller, "~> 1.0"},
|
{:telemetry_poller, "~> 1.0"},
|
||||||
{:gettext, "~> 0.18"},
|
{:gettext, "~> 0.18"},
|
||||||
|
2
mix.lock
2
mix.lock
@ -24,6 +24,7 @@
|
|||||||
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
|
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
|
||||||
"jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"},
|
"jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"},
|
||||||
"mime": {:hex, :mime, "2.0.2", "0b9e1a4c840eafb68d820b0e2158ef5c49385d17fb36855ac6e7e087d4b1dcc5", [:mix], [], "hexpm", "e6a3f76b4c277739e36c2e21a2c640778ba4c3846189d5ab19f97f126df5f9b7"},
|
"mime": {:hex, :mime, "2.0.2", "0b9e1a4c840eafb68d820b0e2158ef5c49385d17fb36855ac6e7e087d4b1dcc5", [:mix], [], "hexpm", "e6a3f76b4c277739e36c2e21a2c640778ba4c3846189d5ab19f97f126df5f9b7"},
|
||||||
|
"oban": {:hex, :oban, "2.10.1", "202a90f2aed0130b7d750bdbfea8090c8321bce255bade10fd3699733565add0", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "161cdd01194147cd6a3efdb1d6c3d9689309991412f799c1e242c18912e307c3"},
|
||||||
"phoenix": {:hex, :phoenix, "1.6.6", "281c8ce8dccc9f60607346b72cdfc597c3dde134dd9df28dff08282f0b751754", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.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", "807bd646e64cd9dc83db016199715faba72758e6db1de0707eef0a2da4924364"},
|
"phoenix": {:hex, :phoenix, "1.6.6", "281c8ce8dccc9f60607346b72cdfc597c3dde134dd9df28dff08282f0b751754", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.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", "807bd646e64cd9dc83db016199715faba72758e6db1de0707eef0a2da4924364"},
|
||||||
"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.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"},
|
"phoenix_html": {:hex, :phoenix_html, "3.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"},
|
||||||
@ -31,6 +32,7 @@
|
|||||||
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [: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", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"},
|
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [: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", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"},
|
||||||
"phoenix_live_view": {:hex, :phoenix_live_view, "0.17.6", "3665f3ec426ac8d681cd7753ad4c85d2d247094dc4dc6add80dd6e3026045389", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.5.9 or ~> 1.6.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "62f06d4bbfc4dc5595070bc338119ab08e8e67a011e2923f9366419622149b9c"},
|
"phoenix_live_view": {:hex, :phoenix_live_view, "0.17.6", "3665f3ec426ac8d681cd7753ad4c85d2d247094dc4dc6add80dd6e3026045389", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.5.9 or ~> 1.6.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "62f06d4bbfc4dc5595070bc338119ab08e8e67a011e2923f9366419622149b9c"},
|
||||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
|
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
|
||||||
|
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.0.0", "ac7cb8971935439c9757b266ac28cf90a940f506833fb548b1795cb5645c36a0", [: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", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "49341c80657f0ef3a65b033d7a47e743463fbe5e950f4c72d83e3a60042c2ad3"},
|
||||||
"phoenix_view": {:hex, :phoenix_view, "1.1.1", "11a945509d1270ef42bb19ca8f609aab378fc02543c2d4bdb8d243f1ca88f9b5", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "1f8148a9163ca4b40065ef177c9c56c0df174cf3596a27995a1a1657f0c949e7"},
|
"phoenix_view": {:hex, :phoenix_view, "1.1.1", "11a945509d1270ef42bb19ca8f609aab378fc02543c2d4bdb8d243f1ca88f9b5", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "1f8148a9163ca4b40065ef177c9c56c0df174cf3596a27995a1a1657f0c949e7"},
|
||||||
"plug": {:hex, :plug, "1.12.1", "645678c800601d8d9f27ad1aebba1fdb9ce5b2623ddb961a074da0b96c35187d", [: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", "d57e799a777bc20494b784966dc5fbda91eb4a09f571f76545b72a634ce0d30b"},
|
"plug": {:hex, :plug, "1.12.1", "645678c800601d8d9f27ad1aebba1fdb9ce5b2623ddb961a074da0b96c35187d", [: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", "d57e799a777bc20494b784966dc5fbda91eb4a09f571f76545b72a634ce0d30b"},
|
||||||
"plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"},
|
"plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"},
|
||||||
|
11
priv/gettext/default.pot
Normal file
11
priv/gettext/default.pot
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
## This file is a PO Template file.
|
||||||
|
##
|
||||||
|
## "msgid"s here are often extracted from source code.
|
||||||
|
## Add new translations manually only if they're dynamic
|
||||||
|
## translations that can't be statically extracted.
|
||||||
|
##
|
||||||
|
## Run "mix gettext.extract" to bring this file up to
|
||||||
|
## date. Leave "msgstr"s empty as changing them here has no
|
||||||
|
## effect: edit them in PO (.po) files instead.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
88
priv/gettext/emails.pot
Normal file
88
priv/gettext/emails.pot
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
## This file is a PO Template file.
|
||||||
|
##
|
||||||
|
## "msgid"s here are often extracted from source code.
|
||||||
|
## Add new translations manually only if they're dynamic
|
||||||
|
## translations that can't be statically extracted.
|
||||||
|
##
|
||||||
|
## Run "mix gettext.extract" to bring this file up to
|
||||||
|
## date. Leave "msgstr"s empty as changing them here has no
|
||||||
|
## effect: edit them in PO (.po) files instead.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, elixir-format, ex-autogen
|
||||||
|
#: lib/cannery_web/templates/layout/email.html.heex:13
|
||||||
|
msgid "This email was sent from %{name}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, elixir-format, ex-autogen
|
||||||
|
#: lib/cannery_web/templates/layout/email.txt.eex:10
|
||||||
|
msgid "This email was sent from %{name} at %{url}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, elixir-format, ex-autogen
|
||||||
|
#: lib/cannery/accounts/email.ex:34
|
||||||
|
msgid "Confirm your %{name} account"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, elixir-format, ex-autogen
|
||||||
|
#: lib/cannery/accounts/email.ex:42
|
||||||
|
msgid "Reset your %{name} password"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, elixir-format, ex-autogen
|
||||||
|
#: lib/cannery/accounts/email.ex:50
|
||||||
|
msgid "Update your %{name} email"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, elixir-format, ex-autogen
|
||||||
|
#: lib/cannery_web/templates/email/confirm_email.html.eex:2
|
||||||
|
#: lib/cannery_web/templates/email/confirm_email.txt.eex:2
|
||||||
|
#: lib/cannery_web/templates/email/reset_password.html.eex:2
|
||||||
|
#: lib/cannery_web/templates/email/reset_password.txt.eex:2
|
||||||
|
#: lib/cannery_web/templates/email/update_email.html.eex:2
|
||||||
|
#: lib/cannery_web/templates/email/update_email.txt.eex:2
|
||||||
|
msgid "Hi %{email},"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, elixir-format, ex-autogen
|
||||||
|
#: lib/cannery_web/templates/email/confirm_email.html.eex:10
|
||||||
|
#: lib/cannery_web/templates/email/confirm_email.txt.eex:10
|
||||||
|
msgid "If you didn't create an account at %{url}, please ignore this."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, elixir-format, ex-autogen
|
||||||
|
#: lib/cannery_web/templates/email/reset_password.html.eex:8
|
||||||
|
#: lib/cannery_web/templates/email/reset_password.txt.eex:8
|
||||||
|
#: lib/cannery_web/templates/email/update_email.html.eex:8
|
||||||
|
#: lib/cannery_web/templates/email/update_email.txt.eex:8
|
||||||
|
msgid "If you didn't request this change from %{url}, please ignore this."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, elixir-format, ex-autogen
|
||||||
|
#: lib/cannery_web/templates/email/confirm_email.html.eex:4
|
||||||
|
msgid "Welcome to %{name}!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, elixir-format, ex-autogen
|
||||||
|
#: lib/cannery_web/templates/email/confirm_email.txt.eex:4
|
||||||
|
msgid "Welcome to %{name}%!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, elixir-format, ex-autogen
|
||||||
|
#: lib/cannery_web/templates/email/update_email.html.eex:4
|
||||||
|
#: lib/cannery_web/templates/email/update_email.txt.eex:4
|
||||||
|
msgid "You can change your email by visiting the URL below:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, elixir-format, ex-autogen
|
||||||
|
#: lib/cannery_web/templates/email/confirm_email.html.eex:6
|
||||||
|
#: lib/cannery_web/templates/email/confirm_email.txt.eex:6
|
||||||
|
msgid "You can confirm your account by visiting the URL below:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#, elixir-format, ex-autogen
|
||||||
|
#: lib/cannery_web/templates/email/reset_password.html.eex:4
|
||||||
|
#: lib/cannery_web/templates/email/reset_password.txt.eex:4
|
||||||
|
msgid "You can reset your password by visiting the URL below:"
|
||||||
|
msgstr ""
|
13
priv/repo/migrations/20220209003648_add_oban_jobs_table.exs
Normal file
13
priv/repo/migrations/20220209003648_add_oban_jobs_table.exs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
defmodule Cannery.Repo.Migrations.AddObanJobsTable do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
Oban.Migrations.up()
|
||||||
|
end
|
||||||
|
|
||||||
|
# We specify `version: 1` in `down`, ensuring that we'll roll all the way back down if
|
||||||
|
# necessary, regardless of which version we've migrated `up` to.
|
||||||
|
def down do
|
||||||
|
Oban.Migrations.down(version: 1)
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user