add swoosh and oban

This commit is contained in:
shibao 2022-02-08 19:59:23 -05:00
parent acf64cee18
commit a39c3da351
26 changed files with 365 additions and 84 deletions

View File

@ -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".

View File

@ -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

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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 """

View 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

View 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

View File

@ -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

View File

@ -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

View File

@ -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

View 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)) %>

View 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)) %>

View 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)) %>

View 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)) %>

View 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)) %>

View 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)) %>

View 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>

View 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)) %>

View 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

View File

@ -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"},

View File

@ -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
View 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
View 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 ""

View 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