forked from shibao/cannery
add emails
This commit is contained in:
parent
34118299e9
commit
9e517e6477
@ -18,7 +18,8 @@ config :lokal, LokalWeb.Endpoint,
|
||||
secret_key_base: "KH59P0iZixX5gP/u+zkxxG8vAAj6vgt0YqnwEB5JP5K+E567SsqkCz69uWShjE7I",
|
||||
render_errors: [view: LokalWeb.ErrorView, accepts: ~w(html json), layout: false],
|
||||
pubsub_server: Lokal.PubSub,
|
||||
live_view: [signing_salt: "zOLgd3lr"]
|
||||
live_view: [signing_salt: "zOLgd3lr"],
|
||||
registration: System.get_env("REGISTRATION") || "invite"
|
||||
|
||||
config :lokal, Lokal.Application, automigrate: false
|
||||
|
||||
@ -39,6 +40,15 @@ config :lokal, Lokal.Mailer, adapter: Swoosh.Adapters.Local
|
||||
# Swoosh API client is needed for adapters other than SMTP.
|
||||
config :swoosh, :api_client, false
|
||||
|
||||
# Gettext
|
||||
config :gettext, :default_locale, "en_US"
|
||||
|
||||
# Configure Oban
|
||||
config :lokal, Oban,
|
||||
repo: Lokal.Repo,
|
||||
plugins: [Oban.Plugins.Pruner],
|
||||
queues: [default: 10, mailers: 20]
|
||||
|
||||
# Configure esbuild (the version is required)
|
||||
# config :esbuild,
|
||||
# version: "0.14.0",
|
||||
|
@ -2,9 +2,6 @@ import Config
|
||||
|
||||
# Configure your database
|
||||
config :lokal, Lokal.Repo,
|
||||
url:
|
||||
System.get_env("DATABASE_URL") ||
|
||||
"ecto://postgres:postgres@localhost/lokal_dev",
|
||||
show_sensitive_data_on_connection_error: true,
|
||||
pool_size: 10
|
||||
|
||||
@ -15,13 +12,10 @@ config :lokal, Lokal.Repo,
|
||||
# watchers to your application. For example, we use it
|
||||
# with esbuild to bundle .js and .css sources.
|
||||
config :lokal, LokalWeb.Endpoint,
|
||||
# Binding to loopback ipv4 address prevents access from other machines.
|
||||
# Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
|
||||
http: [ip: {0, 0, 0, 0}, port: 4000],
|
||||
check_origin: false,
|
||||
code_reloader: true,
|
||||
debug_errors: true,
|
||||
secret_key_base: "cSLRa17z1D1qLwQuaw73DMT7BX8oDMkru/rJIsmCdlFypLGRQW3bpqJRrZQtoZJQ",
|
||||
secret_key_base: "dg2lccMgaY3+ZeKppR+ondk4ZRaANZGIN0LMZT1u1uzscH4jO5W9a9b9V9BkC+MW",
|
||||
watchers: [
|
||||
# Start the esbuild watcher by calling Esbuild.install_and_run(:default, args)
|
||||
# esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]}
|
||||
|
@ -9,16 +9,7 @@ import Config
|
||||
# manifest is generated by the `mix phx.digest` task,
|
||||
# which you should run after static files are built and
|
||||
# before starting your production server.
|
||||
config :lokal, LokalWeb.Endpoint,
|
||||
url: [host: "localhost"],
|
||||
http: [port: 4000],
|
||||
cache_static_manifest: "priv/static/cache_manifest.json"
|
||||
|
||||
config :lokal, Lokal.Repo,
|
||||
url: "ecto://postgres:postgres@localhost/lokal",
|
||||
pool_size: 10
|
||||
|
||||
config :lokal, Lokal.Application, automigrate: true
|
||||
config :lokal, LokalWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json"
|
||||
|
||||
# Do not print debug messages in production
|
||||
config :logger, level: :info
|
||||
|
@ -12,19 +12,49 @@ if System.get_env("PHX_SERVER") && System.get_env("RELEASE_NAME") do
|
||||
config :lokal, LokalWeb.Endpoint, server: true
|
||||
end
|
||||
|
||||
if config_env() == :prod do
|
||||
database_url =
|
||||
config :lokal, LokalWeb.ViewHelpers, shibao_mode: System.get_env("SHIBAO_MODE") == "true"
|
||||
|
||||
# Set locale
|
||||
Gettext.put_locale(System.get_env("LOCALE") || "en_US")
|
||||
|
||||
maybe_ipv6 = if System.get_env("ECTO_IPV6") == "true", do: [:inet6], else: []
|
||||
|
||||
database_url =
|
||||
if config_env() == :test do
|
||||
System.get_env("TEST_DATABASE_URL") ||
|
||||
"ecto://postgres:postgres@localhost/lokal_test#{System.get_env("MIX_TEST_PARTITION")}"
|
||||
else
|
||||
System.get_env("DATABASE_URL") ||
|
||||
"ecto://postgres:postgres@lokal-db/lokal"
|
||||
end
|
||||
|
||||
maybe_ipv6 = if System.get_env("ECTO_IPV6"), do: [:inet6], else: []
|
||||
host =
|
||||
System.get_env("HOST") ||
|
||||
raise "No hostname set! Must be the domain and tld like `lokal.bubbletea.dev`."
|
||||
|
||||
config :lokal, Lokal.Repo,
|
||||
# ssl: true,
|
||||
url: database_url,
|
||||
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
|
||||
socket_options: maybe_ipv6
|
||||
interface =
|
||||
if config_env() in [:dev, :test],
|
||||
do: {0, 0, 0, 0},
|
||||
else: {0, 0, 0, 0, 0, 0, 0, 0}
|
||||
|
||||
config :lokal, Lokal.Repo,
|
||||
# ssl: true,
|
||||
url: database_url,
|
||||
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
|
||||
socket_options: maybe_ipv6
|
||||
|
||||
config :lokal, LokalWeb.Endpoint,
|
||||
url: [scheme: "https", host: host, port: 443],
|
||||
http: [
|
||||
# See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html
|
||||
# for details about using IPv6 vs IPv4 and loopback vs public addresses.
|
||||
ip: interface,
|
||||
port: String.to_integer(System.get_env("PORT") || "4000")
|
||||
],
|
||||
server: true,
|
||||
registration: System.get_env("REGISTRATION") || "invite"
|
||||
|
||||
if config_env() == :prod do
|
||||
# The secret key base is used to sign/encrypt cookies and other secrets.
|
||||
# A default value is used in config/dev.exs and config/test.exs but you
|
||||
# want to use a different value for prod and you most likely don't want
|
||||
@ -37,20 +67,21 @@ if config_env() == :prod do
|
||||
You can generate one by calling: mix phx.gen.secret
|
||||
"""
|
||||
|
||||
host = System.get_env("HOST") || "localhost"
|
||||
config :lokal, LokalWeb.Endpoint, secret_key_base: secret_key_base
|
||||
|
||||
config :lokal, LokalWeb.Endpoint,
|
||||
ururl: [scheme: "https", host: host, port: 443],
|
||||
http: [
|
||||
# Enable IPv6 and bind on all interfaces.
|
||||
# Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access.
|
||||
# See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html
|
||||
# for details about using IPv6 vs IPv4 and loopback vs public addresses.
|
||||
ip: {0, 0, 0, 0, 0, 0, 0, 0},
|
||||
port: String.to_integer(System.get_env("PORT") || "4000")
|
||||
],
|
||||
secret_key_base: secret_key_base,
|
||||
server: true
|
||||
# Automatically apply migrations
|
||||
config :lokal, Lokal.Application, automigrate: true
|
||||
|
||||
# Set up SMTP settings
|
||||
config :lokal, Lokal.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") || "Lokal"
|
||||
|
||||
# ## Using releases
|
||||
#
|
||||
@ -61,22 +92,4 @@ if config_env() == :prod do
|
||||
#
|
||||
# Then you can assemble a release by calling `mix release`.
|
||||
# See `mix help release` for more information.
|
||||
|
||||
# ## Configuring the mailer
|
||||
#
|
||||
# In production you need to configure the mailer to use a different adapter.
|
||||
# Also, you may need to configure the Swoosh API client of your choice if you
|
||||
# are not using SMTP. Here is an example of the configuration:
|
||||
#
|
||||
# config :lokal, Lokal.Mailer,
|
||||
# adapter: Swoosh.Adapters.Mailgun,
|
||||
# api_key: System.get_env("MAILGUN_API_KEY"),
|
||||
# domain: System.get_env("MAILGUN_DOMAIN")
|
||||
#
|
||||
# For this example you need include a HTTP client required by Swoosh API client.
|
||||
# Swoosh supports Hackney and Finch out of the box:
|
||||
#
|
||||
# config :swoosh, :api_client, Swoosh.ApiClient.Hackney
|
||||
#
|
||||
# See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details.
|
||||
end
|
||||
|
@ -1,14 +1,14 @@
|
||||
import Config
|
||||
|
||||
# Only in tests, remove the complexity from the password hashing algorithm
|
||||
config :bcrypt_elixir, :log_rounds, 1
|
||||
|
||||
# Configure your database
|
||||
#
|
||||
# The MIX_TEST_PARTITION environment variable can be used
|
||||
# to provide built-in test partitioning in CI environment.
|
||||
# Run `mix help test` for more information.
|
||||
config :lokal, Lokal.Repo,
|
||||
url:
|
||||
System.get_env("TEST_DATABASE_URL") ||
|
||||
"ecto://postgres:postgres@localhost/lokal_test#{System.get_env("MIX_TEST_PARTITION")}",
|
||||
pool: Ecto.Adapters.SQL.Sandbox,
|
||||
pool_size: 10
|
||||
|
||||
@ -16,7 +16,7 @@ config :lokal, Lokal.Repo,
|
||||
# you can enable the server option below.
|
||||
config :lokal, LokalWeb.Endpoint,
|
||||
http: [ip: {0, 0, 0, 0}, port: 4002],
|
||||
secret_key_base: "T4DkRImgeMNCcPcTWBCZyKYp3KQ8yyPD33VT4wj6ogbP8fIGUsqmOTNX3clTMrLo",
|
||||
secret_key_base: "S3qq9QtUdsFtlYej+HTjAVN95uP5i5tf2sPYINWSQfCKJghFj2B1+wTAoljZyHOK",
|
||||
server: false
|
||||
|
||||
# In test we don't send emails.
|
||||
@ -27,3 +27,6 @@ config :logger, level: :warn
|
||||
|
||||
# Initialize plugs at runtime for faster test compilation
|
||||
config :phoenix, :plug_init_mode, :runtime
|
||||
|
||||
# Disable Oban
|
||||
config :lokal, Oban, queues: false, plugins: false
|
||||
|
124
contributing.md
Normal file
124
contributing.md
Normal file
@ -0,0 +1,124 @@
|
||||
# Contribution Guide
|
||||
|
||||
## Style Tips
|
||||
|
||||
- In order to keep code concise and improve readability, please try to make your
|
||||
functions as short as possible while keeping variable names descriptive! For
|
||||
instance, use inline `do:` blocks for short functions and make your aliases as
|
||||
short as possible without introducing ambiguity.
|
||||
- I.e. since there's only one `Changeset` in the app, please alias
|
||||
`Changeset.t(Type.t())` instead of using `Ecto.Changeset.t(Long.Type.t())`
|
||||
- Use pipelines when possible. If only calling a single method, a pipeline isn't
|
||||
strictly necessary but still encouraged for future modification.
|
||||
- Please add typespecs to your functions! Even your private functions may be
|
||||
used by others later down the line, and typespecs will be able to help
|
||||
document your code just a little bit better, and improve the debugging
|
||||
process.
|
||||
- Typespec arguments can be named like `@spec function(arg_name :: type()) ::
|
||||
return_type()`. Please use these for generic types, such as `map()` when the
|
||||
input data isn't immediately obvious.
|
||||
- Please define all typespecs for a function together in one place, instead of
|
||||
each function header.
|
||||
- When making new models, please take inspiration from the existing models in
|
||||
regards to layout of sections, typespec design, and formatting.
|
||||
- With Elixir convention, for methods that raise on error please name them like
|
||||
`function_that_raises!()`, and functions that return a boolean like
|
||||
`function_that_returns_boolean?()`. For other methods, it's encouraged to use
|
||||
status tuples for other functions like `{:ok, result}` or `{:error,
|
||||
reason_or_changeset}` instead of just returning `result` or `nil` for easy
|
||||
pattern matching.
|
||||
- Instead of using the `.` operator, try to use pattern matching instead,
|
||||
especially for function headers. `.` in templates is fine to keep things
|
||||
concise.
|
||||
- Use `Enum` functions over comprehensions whenever possible for clarity.
|
||||
However, comprehensions in templates are fine for legibility.
|
||||
- When adding text, please use `gettext` macros to enable things to be
|
||||
translated in the future. After adding `gettext` macros, run `mix format` in
|
||||
order to add your new text strings to the files in `priv/gettext`.
|
||||
- Existing domains: `"default"` (for anything general), `"prompts"`
|
||||
(informational messages as a result of the user doing an action, i.e. in
|
||||
flashes), `"actions"` (actions that the user can take), `"emails"`, and
|
||||
`"errors"`. Using these domains accurately will let translators know which
|
||||
translations are higher and lower priority. Thank you!
|
||||
- Before submitting a PR, please make sure all tests are passing using `mix
|
||||
test`.
|
||||
|
||||
# Technical Information
|
||||
|
||||
- Created using the [Phoenix Framework](https://www.phoenixframework.org)
|
||||
- User Registration/Sign in via
|
||||
[`phx_gen_auth`](https://hexdocs.pm/phx_gen_auth/).
|
||||
- `Dockerfile` and example `docker-compose.yml`
|
||||
- Automatic migrations in `MIX_ENV=prod` or Docker image
|
||||
- JS linting with [standard.js](https://standardjs.com), HEEx linting with
|
||||
[heex_formatter](https://github.com/feliperenan/heex_formatter)
|
||||
|
||||
## Docs
|
||||
|
||||
More information can be found in the documentation generated by `mix docs`.
|
||||
These are located in the `/docs` folder, and are generated in HTML and ePub.
|
||||
Check them out!
|
||||
|
||||
# Instructions
|
||||
|
||||
1. Clone the repo
|
||||
1. Install the elixir and erlang binaries. I recommend using [asdf version
|
||||
manager](https://asdf-vm.com/guide/getting-started.html#_1-install-dependencies),
|
||||
which will use the `.tool-versions` file to install the correct versions of
|
||||
Erlang, Elixir and npm for this project!
|
||||
1. Run `mix deps.get` and `mix compile` to fetch all dependencies
|
||||
1. Run `mix setup` to initialize your database. You can reset your database at
|
||||
any time with `mix ecto.reset`.
|
||||
1. Run migrations with `mix ecto.migrate` or rollback with `mix ecto.rollback`.
|
||||
1. Run `mix phx.server` to start the development server.
|
||||
|
||||
# Configuration
|
||||
|
||||
For development, I recommend setting environment variables with
|
||||
[direnv](https://direnv.net).
|
||||
|
||||
By default, Lokal will always bind to all external IPv4 and IPv6 addresses in
|
||||
`dev` and `prod` mode, respectively. If you would like to use different values,
|
||||
they will need to be overridden in `config/dev.exs` and `config/runtime.exs` for
|
||||
`dev` and `prod` modes, respectively.
|
||||
|
||||
## `MIX_ENV=dev`
|
||||
|
||||
In `dev` mode, Lokal will listen for these environment variables at runtime.
|
||||
|
||||
- `HOST`: External url to generate links with. Set this especially if you're
|
||||
behind a reverse proxy. Defaults to `localhost`. External URLs will always be
|
||||
generated with `https://` and port `443`.
|
||||
- `PORT`: Internal port to bind to. Defaults to `4000`.
|
||||
- `DATABASE_URL`: Controls the database url to connect to. Defaults to
|
||||
`ecto://postgres:postgres@localhost/lokal_dev`.
|
||||
- `ECTO_IPV6`: Controls if Ecto should use IPv6 to connect to PostgreSQL.
|
||||
Defaults to `false`.
|
||||
- `POOL_SIZE`: Controls the pool size to use with PostgreSQL. Defaults to `10`.
|
||||
- `REGISTRATION`: Controls if user sign-up should be invite only or set to public. Set to `public` to enable public registration. Defaults to `invite`.
|
||||
- `LOCALE`: Sets a custom locale. Defaults to `en_US`.
|
||||
|
||||
## `MIX_ENV=test`
|
||||
|
||||
In `test` mode (or in the Docker container), Lokal will listen for the same environment variables as dev mode, but also include the following at runtime:
|
||||
|
||||
- `TEST_DATABASE_URL`: REPLACES `DATABASE_URL`. Controls the database url to
|
||||
connect to. Defaults to `ecto://postgres:postgres@localhost/lokal_test`.
|
||||
- `MIX_TEST_PARTITION`: Only used if `TEST_DATABASE_URL` is not specified.
|
||||
Appended to the default database url if you would like to partition your test
|
||||
databases. Defaults to not set.
|
||||
|
||||
## `MIX_ENV=prod`
|
||||
|
||||
In `prod` mode (or in the Docker container), Lokal will listen for the same environment variables as dev mode, but also include the following at runtime:
|
||||
|
||||
- `SECRET_KEY_BASE`: Secret key base used to sign cookies. Must be generated
|
||||
with `docker run -it shibaobun/lokal 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 "Lokal".
|
48
lib/lokal/accounts/email.ex
Normal file
48
lib/lokal/accounts/email.ex
Normal file
@ -0,0 +1,48 @@
|
||||
defmodule Lokal.Email do
|
||||
@moduledoc """
|
||||
Emails that can be sent using Swoosh.
|
||||
|
||||
You can find the base email templates at
|
||||
`lib/Lokal_web/templates/layout/email.html.heex` for html emails and
|
||||
`lib/Lokal_web/templates/layout/email.txt.heex` for text emails.
|
||||
"""
|
||||
|
||||
use Phoenix.Swoosh, view: LokalWeb.EmailView, layout: {LokalWeb.LayoutView, :email}
|
||||
import LokalWeb.Gettext
|
||||
alias Lokal.Accounts.User
|
||||
alias LokalWeb.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
|
||||
from = Application.get_env(:Lokal, Lokal.Mailer)[:email_from] || "noreply@localhost"
|
||||
name = Application.get_env(:Lokal, Lokal.Mailer)[:email_name]
|
||||
new() |> to(email) |> from({name, from}) |> subject(subject)
|
||||
end
|
||||
|
||||
@spec generate_email(key :: String.t(), User.t(), attrs :: map()) :: t()
|
||||
def generate_email("welcome", user, %{"url" => url}) do
|
||||
user
|
||||
|> base_email(dgettext("emails", "Confirm your %{name} account", name: "Lokal"))
|
||||
|> render_body("confirm_email.html", %{user: user, url: url})
|
||||
|> text_body(EmailView.render("confirm_email.txt", %{user: user, url: url}))
|
||||
end
|
||||
|
||||
def generate_email("reset_password", user, %{"url" => url}) do
|
||||
user
|
||||
|> base_email(dgettext("emails", "Reset your %{name} password", name: "Lokal"))
|
||||
|> render_body("reset_password.html", %{user: user, url: url})
|
||||
|> text_body(EmailView.render("reset_password.txt", %{user: user, url: url}))
|
||||
end
|
||||
|
||||
def generate_email("update_email", user, %{"url" => url}) do
|
||||
user
|
||||
|> base_email(dgettext("emails", "Update your %{name} email", name: "Lokal"))
|
||||
|> render_body("update_email.html", %{user: user, url: url})
|
||||
|> text_body(EmailView.render("update_email.txt", %{user: user, url: url}))
|
||||
end
|
||||
end
|
13
lib/lokal/accounts/email_worker.ex
Normal file
13
lib/lokal/accounts/email_worker.ex
Normal file
@ -0,0 +1,13 @@
|
||||
defmodule Lokal.EmailWorker do
|
||||
@moduledoc """
|
||||
Oban worker that dispatches emails
|
||||
"""
|
||||
|
||||
use Oban.Worker, queue: :mailers, tags: ["email"]
|
||||
alias Lokal.{Accounts, Email, Mailer}
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(%Oban.Job{args: %{"email" => email, "user_id" => user_id, "attrs" => attrs}}) do
|
||||
Email.generate_email(email, user_id |> Accounts.get_user!(), attrs) |> Mailer.deliver()
|
||||
end
|
||||
end
|
@ -1,7 +1,42 @@
|
||||
defmodule Lokal.Mailer do
|
||||
@moduledoc """
|
||||
Mailer, currently uses Swoosh
|
||||
Mailer adapter for emails
|
||||
|
||||
Since emails are loaded as Oban jobs, the `:attrs` map must be serializable to
|
||||
json with Jason, which restricts the use of structs.
|
||||
"""
|
||||
|
||||
use Swoosh.Mailer, otp_app: :lokal
|
||||
alias Lokal.{Accounts.User, EmailWorker}
|
||||
alias Oban.Job
|
||||
|
||||
@doc """
|
||||
Deliver instructions to confirm account.
|
||||
"""
|
||||
@spec deliver_confirmation_instructions(User.t(), String.t()) :: Job.t()
|
||||
def deliver_confirmation_instructions(%User{id: user_id}, url) do
|
||||
%{email: :welcome, user_id: user_id, attrs: %{url: url}}
|
||||
|> EmailWorker.new()
|
||||
|> Oban.insert!()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deliver instructions to reset a user password.
|
||||
"""
|
||||
@spec deliver_reset_password_instructions(User.t(), String.t()) :: Job.t()
|
||||
def deliver_reset_password_instructions(%User{id: user_id}, url) do
|
||||
%{email: :reset_password, user_id: user_id, attrs: %{url: url}}
|
||||
|> EmailWorker.new()
|
||||
|> Oban.insert!()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deliver instructions to update a user email.
|
||||
"""
|
||||
@spec deliver_update_email_instructions(User.t(), String.t()) :: Job.t()
|
||||
def deliver_update_email_instructions(%User{id: user_id}, url) do
|
||||
%{email: :update_email, user_id: user_id, attrs: %{url: url}}
|
||||
|> EmailWorker.new()
|
||||
|> Oban.insert!()
|
||||
end
|
||||
end
|
||||
|
25
lib/lokal_web/templates/email/confirm_email.html.eex
Normal file
25
lib/lokal_web/templates/email/confirm_email.html.eex
Normal file
@ -0,0 +1,25 @@
|
||||
<div style="display: flex; flex-direction: column; justify-content: center; align-items: center;">
|
||||
<span style="margin-bottom: 0.75em; font-size: 1.5em;">
|
||||
<%= dgettext("emails", "Hi %{email},", email: @user.email) %>
|
||||
</span>
|
||||
|
||||
<br/>
|
||||
|
||||
<span style="margin-bottom: 1em; font-size: 1.25em;">
|
||||
<%= dgettext("emails", "Welcome to %{name}!", name: "Lokal") %>
|
||||
</span>
|
||||
|
||||
<br/>
|
||||
|
||||
<%= dgettext("emails", "You can confirm your account by visiting the URL below:") %>
|
||||
|
||||
<br/>
|
||||
|
||||
<a style="margin: 1em; color: rgb(31, 31, 31);" href="<%= @url %>"><%= @url %></a>
|
||||
|
||||
<br/>
|
||||
|
||||
<%= dgettext("emails",
|
||||
"If you didn't create an account at %{name}, please ignore this.",
|
||||
name: "Lokal") %>
|
||||
</div>
|
12
lib/lokal_web/templates/email/confirm_email.txt.eex
Normal file
12
lib/lokal_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: "Lokal") %>
|
||||
|
||||
<%= 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, PageLive)) %>
|
19
lib/lokal_web/templates/email/reset_password.html.eex
Normal file
19
lib/lokal_web/templates/email/reset_password.html.eex
Normal file
@ -0,0 +1,19 @@
|
||||
<div style="display: flex; flex-direction: column; justify-content: center; align-items: center;">
|
||||
<span style="margin-bottom: 0.5em; font-size: 1.5em;">
|
||||
<%= dgettext("emails", "Hi %{email},", email: @user.email) %>
|
||||
</span>
|
||||
|
||||
<br/>
|
||||
|
||||
<%= dgettext("emails", "You can reset your password by visiting the URL below:") %>
|
||||
|
||||
<br/>
|
||||
|
||||
<a style="margin: 1em; color: rgb(31, 31, 31);" href="<%= @url %>"><%= @url %></a>
|
||||
|
||||
<br/>
|
||||
|
||||
<%= dgettext("emails",
|
||||
"If you didn't request this change from %{name}, please ignore this.",
|
||||
name: "Lokal") %>
|
||||
</div>
|
10
lib/lokal_web/templates/email/reset_password.txt.eex
Normal file
10
lib/lokal_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, PageLive)) %>
|
19
lib/lokal_web/templates/email/update_email.html.eex
Normal file
19
lib/lokal_web/templates/email/update_email.html.eex
Normal file
@ -0,0 +1,19 @@
|
||||
<div style="display: flex; flex-direction: column; justify-content: center; align-items: center;">
|
||||
<span style="margin-bottom: 0.5em; font-size: 1.5em;">
|
||||
<%= dgettext("emails", "Hi %{email},", email: @user.email) %>
|
||||
</span>
|
||||
|
||||
<br/>
|
||||
|
||||
<%= dgettext("emails", "You can change your email by visiting the URL below:") %>
|
||||
|
||||
<br/>
|
||||
|
||||
<a style="margin: 1em; color: rgb(31, 31, 31);" href="<%= @url %>"><%= @url %></a>
|
||||
|
||||
<br/>
|
||||
|
||||
<%= dgettext("emails",
|
||||
"If you didn't request this change from %{name}, please ignore this.",
|
||||
name: "Lokal") %>
|
||||
</div>
|
10
lib/lokal_web/templates/email/update_email.txt.eex
Normal file
10
lib/lokal_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, PageLive)) %>
|
24
lib/lokal_web/templates/layout/email.html.heex
Normal file
24
lib/lokal_web/templates/layout/email.html.heex
Normal file
@ -0,0 +1,24 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>
|
||||
<%= @email.subject %>
|
||||
</title>
|
||||
</head>
|
||||
<body
|
||||
style="padding: 2em; color: rgb(31, 31, 31); background-color: rgb(220, 220, 228); font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; text-align: center;"
|
||||
>
|
||||
<%= @inner_content %>
|
||||
|
||||
<hr
|
||||
style="margin: 2em auto; border-width: 1px; border-color: rgb(212, 212, 216); width: 100%; max-width: 42rem;"
|
||||
/>
|
||||
|
||||
<a style="color: rgb(31, 31, 31);" href={Routes.live_url(Endpoint, PageLive)}>
|
||||
<%= dgettext(
|
||||
"emails",
|
||||
"This email was sent from %{name}, the self-hosted firearm tracker website.",
|
||||
name: "Lokal"
|
||||
) %>
|
||||
</a>
|
||||
</body>
|
||||
</html>
|
12
lib/lokal_web/templates/layout/email.txt.eex
Normal file
12
lib/lokal_web/templates/layout/email.txt.eex
Normal file
@ -0,0 +1,12 @@
|
||||
<%= @email.subject %>
|
||||
|
||||
====================
|
||||
|
||||
<%= @inner_content %>
|
||||
|
||||
=====================
|
||||
|
||||
<%= dgettext("emails",
|
||||
"This email was sent from %{name} at %{url}, the self-hosted firearm tracker website.",
|
||||
name: "Lokal",
|
||||
url: Routes.live_url(Endpoint, PageLive)) %>
|
1
lib/lokal_web/templates/layout/empty.html.heex
Normal file
1
lib/lokal_web/templates/layout/empty.html.heex
Normal file
@ -0,0 +1 @@
|
||||
<%= @inner_content %>
|
8
lib/lokal_web/views/email_view.ex
Normal file
8
lib/lokal_web/views/email_view.ex
Normal file
@ -0,0 +1,8 @@
|
||||
defmodule LokalWeb.EmailView do
|
||||
@moduledoc """
|
||||
A view for email-related helper functions
|
||||
"""
|
||||
alias LokalWeb.{Endpoint, PageLive}
|
||||
|
||||
use LokalWeb, :view
|
||||
end
|
1
mix.exs
1
mix.exs
@ -60,6 +60,7 @@ defmodule Lokal.MixProject do
|
||||
{:ex_doc, "~> 0.27", only: :dev, runtime: false},
|
||||
{:swoosh, "~> 1.6"},
|
||||
{:gen_smtp, "~> 1.0"},
|
||||
{:phoenix_swoosh, "~> 1.0"},
|
||||
{:telemetry_metrics, "~> 0.6"},
|
||||
{:telemetry_poller, "~> 1.0"},
|
||||
{:gettext, "~> 0.18"},
|
||||
|
1
mix.lock
1
mix.lock
@ -40,6 +40,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_view": {:hex, :phoenix_live_view, "0.17.7", "05a42377075868a678d446361effba80cefef19ab98941c01a7a4c7560b29121", [: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", "25eaf41028eb351b90d4f69671874643a09944098fefd0d01d442f40a6091b6f"},
|
||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
|
||||
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.0.1", "0db6eb6405a6b06cae4fdf4144659b3f4fee4553e2856fe8a53ba12e9fb21a74", [: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", "e34890004baec08f0fa12bd8c77bf64bfb4156b84a07fb79da9322fa94bc3781"},
|
||||
"phoenix_view": {:hex, :phoenix_view, "1.1.2", "1b82764a065fb41051637872c7bd07ed2fdb6f5c3bd89684d4dca6e10115c95a", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "7ae90ad27b09091266f6adbb61e1d2516a7c3d7062c6789d46a7554ec40f3a56"},
|
||||
"plug": {:hex, :plug, "1.13.3", "93b299039c21a8b82cc904d13812bce4ced45cf69153e8d35ca16ffb3e8c5d98", [: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", "98c8003e4faf7b74a9ac41bee99e328b08f069bf932747d4a7532e97ae837a17"},
|
||||
"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"},
|
||||
|
52
readme.md
52
readme.md
@ -14,40 +14,42 @@ shopping today!
|
||||
|
||||
# Installation
|
||||
|
||||
1. Clone the repo
|
||||
2. Run `mix setup`
|
||||
3. Run `mix phx.server` to start the development server
|
||||
1. Install [Docker Compose](https://docs.docker.com/compose/install/) or alternatively [Docker Desktop](https://docs.docker.com/desktop/) on your machine.
|
||||
1. Copy the example [docker-compose.yml](https://gitea.bubbletea.dev/shibao/lokal/src/branch/stable/docker-compose.yml). into your local machine where you want.
|
||||
Bind mounts are created in the same directory by default.
|
||||
1. Set the configuration variables in `docker-compose.yml`. You'll need to run
|
||||
`docker run -it shibaobun/lokal /app/priv/random.sh` to generate a new
|
||||
secret key base.
|
||||
1. Use `docker-compose up` or `docker-compose up -d` to start the container!
|
||||
|
||||
The first created user will be created as an admin.
|
||||
|
||||
# Configuration
|
||||
|
||||
For development, I recommend setting environment variables with
|
||||
[direnv](https://direnv.net).
|
||||
You can use the following environment variables to configure Lokal in
|
||||
[docker-compose.yml](https://gitea.bubbletea.dev/shibao/lokal/src/branch/stable/docker-compose.yml).
|
||||
|
||||
## `MIX_ENV=dev`
|
||||
|
||||
In `dev` mode, Lokal will listen for these environment variables on compile.
|
||||
|
||||
- `HOST`: External url to generate links with. Set these especially if you're
|
||||
behind a reverse proxy. Defaults to `localhost`.
|
||||
- `PORT`: External port for urls. Defaults to `443`.
|
||||
- `DATABASE_URL`: Controls the database url to connect to. Defaults to
|
||||
`ecto://postgres:postgres@localhost/lokal_dev`.
|
||||
|
||||
## `MIX_ENV=prod`
|
||||
|
||||
In `prod` mode (or in the Docker container), Lokal will listen for these environment variables at runtime.
|
||||
|
||||
- `HOST`: External url to generate links with. Set these especially if you're
|
||||
behind a reverse proxy. Defaults to `localhost`.
|
||||
- `PORT`: Internal port to bind to. Defaults to `4000` and attempts to bind to
|
||||
`0.0.0.0`. Must be reverse proxied!
|
||||
- `HOST`: External url to generate links with. Must be set with your hosted
|
||||
domain name! I.e. `lokal.mywebsite.tld`
|
||||
- `PORT`: Internal port to bind to. Defaults to `4000`. Must be reverse proxied!
|
||||
- `DATABASE_URL`: Controls the database url to connect to. Defaults to
|
||||
`ecto://postgres:postgres@lokal-db/lokal`.
|
||||
- `ECTO_IPV6`: Controls if Ecto should use ipv6 to connect to PostgreSQL.
|
||||
- `ECTO_IPV6`: If set to `true`, Ecto should use ipv6 to connect to PostgreSQL.
|
||||
Defaults to `false`.
|
||||
- `POOL_SIZE`: Controls the pool size to use with PostgreSQL. Defaults to `10`.
|
||||
- `SECRET_KEY_BASE`: Secret key base used to sign cookies. Must be generated
|
||||
with `mix phx.gen.secret` and set for server to start.
|
||||
with `docker run -it shibaobun/lokal mix phx.gen.secret` and set for server to start.
|
||||
- `REGISTRATION`: Controls if user sign-up should be invite only or set to
|
||||
public. Set to `public` to enable public registration. Defaults to `invite`.
|
||||
- `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 "Lokal".
|
||||
|
||||
---
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user