From 34241a8f83fc4d24513a70c1d1be5a2adf45ebcb Mon Sep 17 00:00:00 2001 From: Robert Prehn Date: Fri, 10 Jul 2020 19:39:38 +0000 Subject: [PATCH] feat: Add email framework --- apps/content/lib/content/telemetry.ex | 3 + apps/content/mix.exs | 4 +- apps/core/lib/core/map_utils.ex | 13 + apps/core/lib/core_email.ex | 19 ++ apps/core/lib/core_mailer.ex | 6 + apps/core/lib/core_web/router.ex | 5 + apps/core/lib/core_web/telemetry.ex | 3 + .../templates/core_email/email.html.eex | 239 +++++++++++++ .../templates/core_email/email.text.eex | 1 + .../lib/core_web/views/core_email_view.ex | 318 ++++++++++++++++++ apps/core/lib/i18n.ex | 18 + apps/core/mix.exs | 14 +- apps/core/mix.lock | 9 + config/config.exs | 11 +- config/dev.exs | 2 + config/email_styles.exs | 85 +++++ config/en.yml | 5 + config/i18n/en.yml | 5 + mix.exs | 7 +- mix.lock | 17 + script/server | 2 +- 21 files changed, 776 insertions(+), 10 deletions(-) create mode 100644 apps/core/lib/core/map_utils.ex create mode 100644 apps/core/lib/core_email.ex create mode 100644 apps/core/lib/core_mailer.ex create mode 100644 apps/core/lib/core_web/templates/core_email/email.html.eex create mode 100644 apps/core/lib/core_web/templates/core_email/email.text.eex create mode 100644 apps/core/lib/core_web/views/core_email_view.ex create mode 100644 apps/core/lib/i18n.ex create mode 100644 config/email_styles.exs create mode 100644 config/en.yml create mode 100644 config/i18n/en.yml diff --git a/apps/content/lib/content/telemetry.ex b/apps/content/lib/content/telemetry.ex index 01208105..2b73f952 100644 --- a/apps/content/lib/content/telemetry.ex +++ b/apps/content/lib/content/telemetry.ex @@ -1,4 +1,7 @@ defmodule Content.Telemetry do + @moduledoc """ + Collects metrics for the application and allows them to be transmitted using the Telemetry framework. + """ use Supervisor import Telemetry.Metrics diff --git a/apps/content/mix.exs b/apps/content/mix.exs index 72134e00..7ae5c95a 100644 --- a/apps/content/mix.exs +++ b/apps/content/mix.exs @@ -14,7 +14,8 @@ defmodule Content.MixProject do compilers: [:phoenix, :gettext] ++ Mix.compilers(), start_permanent: Mix.env() == :prod, aliases: aliases(), - deps: deps() + deps: deps(), + test_coverage: [tool: ExCoveralls], ] end @@ -37,6 +38,7 @@ defmodule Content.MixProject do # Type `mix help deps` for examples and options. defp deps do [ + {:excoveralls, "~> 0.10", only: [:dev, :test]}, {:phoenix, "~> 1.5.3"}, {:phoenix_ecto, "~> 4.0"}, {:phoenix_html, "~> 2.11"}, diff --git a/apps/core/lib/core/map_utils.ex b/apps/core/lib/core/map_utils.ex new file mode 100644 index 00000000..35323238 --- /dev/null +++ b/apps/core/lib/core/map_utils.ex @@ -0,0 +1,13 @@ +defmodule Core.MapUtils do + def deep_merge(base, override) do + Map.merge(base, override, &deep_value/3) + end + + defp deep_value(_key, base = %{}, override = %{}) do + deep_merge(base, override) + end + + defp deep_value(_key, _base, override) do + override + end +end diff --git a/apps/core/lib/core_email.ex b/apps/core/lib/core_email.ex new file mode 100644 index 00000000..1e7f84b5 --- /dev/null +++ b/apps/core/lib/core_email.ex @@ -0,0 +1,19 @@ +defmodule CoreEmail do + @moduledoc """ + The core library for email in the application. The functions here can be composed in the application to send + different emails. + """ + import Bamboo.Email + use Bamboo.Phoenix, view: CoreWeb.CoreEmailView + + def base_email() do + new_email() + |> put_html_layout({CoreWeb.CoreEmailView, "email.html"}) + |> put_text_layout({CoreWeb.CoreEmailView, "email.text"}) + |> from(sender()) + end + + defp sender() do + Application.get_env(:core, :email_from) + end +end diff --git a/apps/core/lib/core_mailer.ex b/apps/core/lib/core_mailer.ex new file mode 100644 index 00000000..4da30d06 --- /dev/null +++ b/apps/core/lib/core_mailer.ex @@ -0,0 +1,6 @@ +defmodule CoreMailer do + @moduledoc """ + The base mailer for email for the application. + """ + use Bamboo.Mailer, otp_app: :core +end diff --git a/apps/core/lib/core_web/router.ex b/apps/core/lib/core_web/router.ex index 38e843c0..dd900340 100644 --- a/apps/core/lib/core_web/router.ex +++ b/apps/core/lib/core_web/router.ex @@ -38,6 +38,11 @@ defmodule CoreWeb.Router do end end + if Mix.env == :dev do + # If using Phoenix + forward "/sent_emails", Bamboo.SentEmailViewerPlug + end + Application.get_env(:core, :router_forwards, []) |> Enum.map(fn router -> forward "/", router diff --git a/apps/core/lib/core_web/telemetry.ex b/apps/core/lib/core_web/telemetry.ex index 192618e9..5c77a538 100644 --- a/apps/core/lib/core_web/telemetry.ex +++ b/apps/core/lib/core_web/telemetry.ex @@ -1,4 +1,7 @@ defmodule CoreWeb.Telemetry do + @moduledoc """ + Collects metrics for the application and allows them to be transmitted using the Telemetry framework. + """ use Supervisor import Telemetry.Metrics diff --git a/apps/core/lib/core_web/templates/core_email/email.html.eex b/apps/core/lib/core_web/templates/core_email/email.html.eex new file mode 100644 index 00000000..96bd84e5 --- /dev/null +++ b/apps/core/lib/core_web/templates/core_email/email.html.eex @@ -0,0 +1,239 @@ + + + + + + + + + + + <%= assigns[:title] %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + <%= if assigns[:preview_text] do %> + <%= preview do %> + <%= assigns[:preview_text] %> + <% end %> + <% end %> + + + + + +
+ + diff --git a/apps/core/lib/core_web/templates/core_email/email.text.eex b/apps/core/lib/core_web/templates/core_email/email.text.eex new file mode 100644 index 00000000..05433985 --- /dev/null +++ b/apps/core/lib/core_web/templates/core_email/email.text.eex @@ -0,0 +1 @@ +<%= @inner_content %> diff --git a/apps/core/lib/core_web/views/core_email_view.ex b/apps/core/lib/core_web/views/core_email_view.ex new file mode 100644 index 00000000..f8c777bd --- /dev/null +++ b/apps/core/lib/core_web/views/core_email_view.ex @@ -0,0 +1,318 @@ +defmodule CoreWeb.CoreEmailView do + use Phoenix.View, + root: "lib/core_web/templates", + namespace: CoreWeb, + pattern: "**/*" + + import Phoenix.HTML, only: [sigil_E: 2] + + def framework_styles do + %{ + background: %{ + color: "#222222", + }, + body: %{ + font_family: "sans-serif", + font_size: "15px", + line_height: "20px", + text_color: "#555555", + }, + button: %{ + border_radius: "4px", + border: "1px solid #000000", + background: "#222222", + color: "#ffffff", + font_family: "sans-serif", + font_size: "15px", + line_height: "15px", + text_decoration: "none", + padding: "13px 17px", + display: "block", + }, + column: %{ + background: "#FFFFFF", + padding: "0 10px 40px 10px", + }, + footer: %{ + padding: "20px", + font_family: "sans-serif", + font_size: "12px", + line_height: "15px", + text_align: "center", + color: "#ffffff", + }, + global: %{ + width: 600, + }, + h1: %{ + margin: "0 0 10px 0", + font_family: "sans-serif", + font_size: "25px", + line_height: "30px", + color: "#333333", + font_weight: "normal", + }, + h2: %{ + margin: "0 0 10px 0", + font_family: "sans-serif", + font_size: "18px", + line_height: "22px", + color: "#333333", + font_weight: "bold", + }, + header: %{ + padding: "20px 0", + text_align: "center", + }, + hero_image: %{ + background: "#dddddd", + display: "block", + margin: "auto", + }, + inner_column: %{ + padding: "10px 10px 0", + }, + li: %{ + margin: "0 0 0 10px", + }, + last_li: %{ + margin: "0 0 10px 30px", + }, + spacer: %{ + height: "40", + font_size: "0px", + line_height: "0px", + }, + ul: %{ + margin: "0 0 10px 0", + padding: "0", + }, + } + end + + def framework_styles(group) do + Map.get(framework_styles(), group, %{}) + end + + def application_styles(group) do + styles = Application.get_env(:core, :email, %{}) |> Map.get(:styles, %{}) + + Map.get(styles, group, %{}) + end + + def effective_styles(group, overrides \\ %{}) do + group + |> framework_styles() + |> Core.MapUtils.deep_merge(application_styles(group)) + |> Map.merge(overrides) + end + + def preview(do: content) do + ~E""" + + + + + +
+ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  + ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌  +
+ """ + end + + def header(do: content) do + ~E""" + + + <%= content %> + + + """ + end + + def spacer do + style = effective_styles(:spacer) + + ~E""" + + +   + + + """ + end + + def row(do: content) do + ~E""" + + + <%= content %> + +
+ """ + end + + def col(n, opts, do: content) do + {of, opts} = Keyword.pop!(opts, :of) + width = n * 100.0 / of + + ~E""" + + + + + +
+ <%= content %> +
+ + """ + end + + def hero_image(opts) do + {src, _rest_opts} = Keyword.pop!(opts, :src) + + ~E""" + <%= row do %> + <%= col 1, of: 1 do %> + alt_text + <% end %> + <% end %> + """ + end + + def h1(do: content) do + ~E""" +

+ <%= content %> +

+ """ + end + + def h2(do: content) do + ~E""" +

+ <%= content %> +

+ """ + end + + def p(do: content) do + ~E""" +

+ <%= content %> +

+ """ + end + + def button(opts, do: content) do + {overrides, opts_without_style} = Keyword.pop(opts, :style, %{}) + {href, _rest_opts} = Keyword.pop!(opts_without_style, :href) + + style = effective_styles(:button, overrides) + cell_style = style |> Map.take([:border_radius, :background]) + + ~E""" + <%= wrapper do %> + + + <%= content %> + + + <% end %> + """ + end + + def ul(opts) do + {items, _rest_opts} = Keyword.pop!(opts, :items) + + item_count = Enum.count(items) + + item_tags = + items + |> Enum.with_index() + |> Enum.map(fn {item, index} -> + li_for_ul(index, item_count, item) + end) + + ~E""" + + """ + end + + def footer do + ~E""" + <%= wrapper do %> + + <%= I18n.t! "en", "email.company.name" %>
+ + <%= I18n.t! "en", "email.company.address" %>
+ <%= I18n.t! "en", "email.company.phone" %> +
+

+ + <% end %> + """ + end + + defp li_for_ul(index, list_length, content) do + last_styles = if index == list_length - 1, do: map_style(effective_styles(:last_li)) + ~E""" +
  • + <%= content %> +
  • + """ + end + + defp wrapper(do: content) do + ~E""" + + + <%= content %> + +
    + """ + end + + defp map_style(map) do + map + |> Enum.map(fn {key, value} -> + new_key = + key + |> Atom.to_string() + |> String.replace("_", "-") + "#{new_key}: #{value};" + end) + |> Enum.join("\n") + end +end diff --git a/apps/core/lib/i18n.ex b/apps/core/lib/i18n.ex new file mode 100644 index 00000000..31035a0d --- /dev/null +++ b/apps/core/lib/i18n.ex @@ -0,0 +1,18 @@ +defmodule I18n do + @moduledoc """ + The internationalization and strings module. Keeps strings outside the codebase and allows them to + be replaced on a per locale basis by editing yml files. + """ + use Linguist.Vocabulary + + Path.join(["../../config/i18n", "*.yml"]) + |> Path.wildcard() + |> Enum.each(fn path -> + locale = + path + |> Path.basename() + |> Path.rootname() + + locale locale, path + end) +end diff --git a/apps/core/mix.exs b/apps/core/mix.exs index c3c19340..78fd6576 100644 --- a/apps/core/mix.exs +++ b/apps/core/mix.exs @@ -5,12 +5,17 @@ defmodule Core.MixProject do [ app: :core, version: "0.1.0", + build_path: "../../_build", + config_path: "../../config/config.exs", + deps_path: "../../deps", + lockfile: "../../mix.lock", elixir: "~> 1.7", elixirc_paths: elixirc_paths(Mix.env()), compilers: [:phoenix, :gettext] ++ Mix.compilers(), start_permanent: Mix.env() == :prod, aliases: aliases(), - deps: deps() + deps: deps(), + test_coverage: [tool: ExCoveralls], ] end @@ -33,9 +38,14 @@ defmodule Core.MixProject do # Type `mix help deps` for examples and options. defp deps do [ + {:bamboo, "~> 1.5"}, + {:excoveralls, "~> 0.10", only: [:dev, :test]}, + {:credo, "~> 1.4", only: [:dev, :test], runtime: false}, + {:ex_cldr, "~> 2.13.0"}, {:phoenix, "~> 1.5.3"}, {:phoenix_ecto, "~> 4.1"}, {:ecto_sql, "~> 3.4"}, + {:linguist, "0.3.0"}, {:postgrex, ">= 0.0.0"}, {:phoenix_html, "~> 2.11"}, {:phoenix_live_reload, "~> 1.2", only: :dev}, @@ -44,7 +54,7 @@ defmodule Core.MixProject do {:telemetry_poller, "~> 0.4"}, {:gettext, "~> 0.11"}, {:jason, "~> 1.0"}, - {:plug_cowboy, "~> 2.0"} + {:plug_cowboy, "~> 2.0"}, ] end diff --git a/apps/core/mix.lock b/apps/core/mix.lock index 52a579d2..a55c4a1f 100644 --- a/apps/core/mix.lock +++ b/apps/core/mix.lock @@ -1,4 +1,6 @@ %{ + "bamboo": {:hex, :bamboo, "1.5.0", "1926107d58adba6620450f254dfe8a3686637a291851fba125686fa8574842af", [:mix], [{:hackney, ">= 1.13.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d5f3d04d154e80176fd685e2531e73870d8700679f14d25a567e448abce6298d"}, + "certifi": {:hex, :certifi, "2.5.2", "b7cfeae9d2ed395695dd8201c57a2d019c0c43ecaf8b8bcb9320b40d6662f340", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "3b3b5f36493004ac3455966991eaf6e768ce9884693d9968055aeeeb1e575040"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"}, "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"}, "cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"}, @@ -8,8 +10,13 @@ "ecto_sql": {:hex, :ecto_sql, "3.4.4", "d28bac2d420f708993baed522054870086fd45016a9d09bb2cd521b9c48d32ea", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "edb49af715dd72f213b66adfd0f668a43c17ed510b5d9ac7528569b23af57fe8"}, "file_system": {:hex, :file_system, "0.2.8", "f632bd287927a1eed2b718f22af727c5aeaccc9a98d8c2bd7bff709e851dc986", [:mix], [], "hexpm", "97a3b6f8d63ef53bd0113070102db2ce05352ecf0d25390eb8d747c2bde98bca"}, "gettext": {:hex, :gettext, "0.18.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"}, + "hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"}, + "idna": {:hex, :idna, "6.0.1", "1d038fb2e7668ce41fbf681d2c45902e52b3cb9e9c77b55334353b222c2ee50c", [:rebar3], [{:unicode_util_compat, "0.5.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a02c8a1c4fd601215bb0b0324c8a6986749f807ce35f25449ec9e69758708122"}, "jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"}, + "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, + "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, "phoenix": {:hex, :phoenix, "1.5.3", "bfe0404e48ea03dfe17f141eff34e1e058a23f15f109885bbdcf62be303b49ff", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8e16febeb9640d8b33895a691a56481464b82836d338bb3a23125cd7b6157c25"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"}, "phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"}, @@ -22,7 +29,9 @@ "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, "postgrex": {:hex, :postgrex, "0.15.5", "aec40306a622d459b01bff890fa42f1430dac61593b122754144ad9033a2152f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ed90c81e1525f65a2ba2279dbcebf030d6d13328daa2f8088b9661eb9143af7f"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"}, "telemetry_metrics": {:hex, :telemetry_metrics, "0.5.0", "1b796e74add83abf844e808564275dfb342bcc930b04c7577ab780e262b0d998", [:mix], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31225e6ce7a37a421a0a96ec55244386aec1c190b22578bd245188a4a33298fd"}, "telemetry_poller": {:hex, :telemetry_poller, "0.5.1", "21071cc2e536810bac5628b935521ff3e28f0303e770951158c73eaaa01e962a", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4cab72069210bc6e7a080cec9afffad1b33370149ed5d379b81c7c5f0c663fd4"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"}, } diff --git a/config/config.exs b/config/config.exs index 956f60b1..a692fbd4 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,12 +1,13 @@ use Mix.Config -config :core, router_forwards: [Content.Router] +config :core, + router_forwards: [Content.Router], + email_from: "example@example.org" config :content, generators: [context_app: false] -config :content, Content.Endpoint, - server: false +config :content, Content.Endpoint, server: false import_config "../apps/*/config/config.exs" @@ -18,6 +19,10 @@ config :logger, :console, # Use Jason for JSON parsing in Phoenix config :phoenix, :json_library, Jason +config :linguist, pluralization_key: :count + +import_config "email_styles.exs" + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" diff --git a/config/dev.exs b/config/dev.exs index fd5feb58..9f86d994 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -55,3 +55,5 @@ config :content, Content.Endpoint, ~r"lib/content/templates/.*(eex)$" ] ] + +config :core, CoreMailer, adapter: Bamboo.LocalAdapter diff --git a/config/email_styles.exs b/config/email_styles.exs new file mode 100644 index 00000000..f983382d --- /dev/null +++ b/config/email_styles.exs @@ -0,0 +1,85 @@ +use Mix.Config + +config :core, :email, %{ + styles: %{ + background: %{ + color: "#222222", + }, + body: %{ + font_family: "sans-serif", + font_size: "15px", + line_height: "20px", + text_color: "#555555", + }, + button: %{ + border_radius: "4px", + border: "1px solid #000000", + background: "#222222", + color: "#ffffff", + font_family: "sans-serif", + font_size: "15px", + line_height: "15px", + text_decoration: "none", + padding: "13px 17px", + display: "block", + }, + column: %{ + background: "#FFFFFF", + padding: "0 10px 40px 10px", + }, + footer: %{ + padding: "20px", + font_family: "sans-serif", + font_size: "12px", + line_height: "15px", + text_align: "center", + color: "#ffffff", + }, + global: %{ + width: 600, + }, + h1: %{ + margin: "0 0 10px 0", + font_family: "sans-serif", + font_size: "25px", + line_height: "30px", + color: "#333333", + font_weight: "normal", + }, + h2: %{ + margin: "0 0 10px 0", + font_family: "sans-serif", + font_size: "18px", + line_height: "22px", + color: "#333333", + font_weight: "bold", + }, + header: %{ + padding: "20px 0", + text_align: "center", + }, + hero_image: %{ + background: "#dddddd", + display: "block", + margin: "auto", + }, + inner_column: %{ + padding: "10px 10px 0", + }, + li: %{ + margin: "0 0 0 10px", + }, + last_li: %{ + margin: "0 0 10px 30px", + }, + spacer: %{ + height: "40", + font_size: "0px", + line_height: "0px", + }, + ul: %{ + margin: "0 0 10px 0", + padding: "0", + }, + } +} diff --git a/config/en.yml b/config/en.yml new file mode 100644 index 00000000..76742d8d --- /dev/null +++ b/config/en.yml @@ -0,0 +1,5 @@ +email: + company: + name: "" + address: "" + phone: "" diff --git a/config/i18n/en.yml b/config/i18n/en.yml new file mode 100644 index 00000000..76742d8d --- /dev/null +++ b/config/i18n/en.yml @@ -0,0 +1,5 @@ +email: + company: + name: "" + address: "" + phone: "" diff --git a/mix.exs b/mix.exs index 1eb8e420..f499ec41 100644 --- a/mix.exs +++ b/mix.exs @@ -4,10 +4,11 @@ defmodule Legendary.Mixfile do def project do [ apps_path: "apps", - build_embedded: Mix.env == :prod, - start_permanent: Mix.env == :prod, + build_embedded: Mix.env() == :prod, + start_permanent: Mix.env() == :prod, deps: deps(), - aliases: aliases() + aliases: aliases(), + test_coverage: [tool: ExCoveralls], ] end diff --git a/mix.lock b/mix.lock index 52a579d2..d6048e19 100644 --- a/mix.lock +++ b/mix.lock @@ -1,15 +1,28 @@ %{ + "bamboo": {:hex, :bamboo, "1.5.0", "1926107d58adba6620450f254dfe8a3686637a291851fba125686fa8574842af", [:mix], [{:hackney, ">= 1.13.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d5f3d04d154e80176fd685e2531e73870d8700679f14d25a567e448abce6298d"}, + "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, + "certifi": {:hex, :certifi, "2.5.2", "b7cfeae9d2ed395695dd8201c57a2d019c0c43ecaf8b8bcb9320b40d6662f340", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "3b3b5f36493004ac3455966991eaf6e768ce9884693d9968055aeeeb1e575040"}, + "cldr_utils": {:hex, :cldr_utils, "2.9.1", "be714403abe1a7abed5ee4f7dd3823a9067f96ab4b0613a454177b51ca204236", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "6cba0a485f57feb773291ca1816469ddd887e22d73d9b12a1b207d82a67a4e71"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"}, "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"}, "cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"}, + "credo": {:hex, :credo, "1.4.0", "92339d4cbadd1e88b5ee43d427b639b68a11071b6f73854e33638e30a0ea11f5", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1fd3b70dce216574ce3c18bdf510b57e7c4c85c2ec9cad4bff854abaf7e58658"}, "db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"}, "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"}, "ecto": {:hex, :ecto, "3.4.5", "2bcd262f57b2c888b0bd7f7a28c8a48aa11dc1a2c6a858e45dd8f8426d504265", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8c6d1d4d524559e9b7a062f0498e2c206122552d63eacff0a6567ffe7a8e8691"}, "ecto_sql": {:hex, :ecto_sql, "3.4.4", "d28bac2d420f708993baed522054870086fd45016a9d09bb2cd521b9c48d32ea", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "edb49af715dd72f213b66adfd0f668a43c17ed510b5d9ac7528569b23af57fe8"}, + "ex_cldr": {:hex, :ex_cldr, "2.13.0", "742f14a4afcfea61a190d603d8e555d2c91d71e4e8fc2520d5dc35616969e225", [:mix], [{:cldr_utils, "~> 2.3", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.13", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "5e4cf3e945ee60156a3342e2a762f69036ffbe1f80520cc88592d68f12c5db55"}, + "excoveralls": {:hex, :excoveralls, "0.13.0", "4e1b7cc4e0351d8d16e9be21b0345a7e165798ee5319c7800b9138ce17e0b38e", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "fe2a56c8909564e2e6764765878d7d5e141f2af3bc8ff3b018a68ee2a218fced"}, "file_system": {:hex, :file_system, "0.2.8", "f632bd287927a1eed2b718f22af727c5aeaccc9a98d8c2bd7bff709e851dc986", [:mix], [], "hexpm", "97a3b6f8d63ef53bd0113070102db2ce05352ecf0d25390eb8d747c2bde98bca"}, "gettext": {:hex, :gettext, "0.18.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"}, + "hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"}, + "idna": {:hex, :idna, "6.0.1", "1d038fb2e7668ce41fbf681d2c45902e52b3cb9e9c77b55334353b222c2ee50c", [:rebar3], [{:unicode_util_compat, "0.5.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a02c8a1c4fd601215bb0b0324c8a6986749f807ce35f25449ec9e69758708122"}, "jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"}, + "linguist": {:hex, :linguist, "0.3.0", "2984dfce6720d1212ddd7bba82496f92627a39aecd4d32c7016ec00393e1f925", [:mix], [{:ex_cldr, "~> 2.0", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.0", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "1923876545db22b63334c9d203ef56397a2946daa018117767b068f856be41e4"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"}, + "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, + "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, "phoenix": {:hex, :phoenix, "1.5.3", "bfe0404e48ea03dfe17f141eff34e1e058a23f15f109885bbdcf62be303b49ff", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8e16febeb9640d8b33895a691a56481464b82836d338bb3a23125cd7b6157c25"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"}, "phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"}, @@ -22,7 +35,11 @@ "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, "postgrex": {:hex, :postgrex, "0.15.5", "aec40306a622d459b01bff890fa42f1430dac61593b122754144ad9033a2152f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ed90c81e1525f65a2ba2279dbcebf030d6d13328daa2f8088b9661eb9143af7f"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"}, "telemetry_metrics": {:hex, :telemetry_metrics, "0.5.0", "1b796e74add83abf844e808564275dfb342bcc930b04c7577ab780e262b0d998", [:mix], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31225e6ce7a37a421a0a96ec55244386aec1c190b22578bd245188a4a33298fd"}, "telemetry_poller": {:hex, :telemetry_poller, "0.5.1", "21071cc2e536810bac5628b935521ff3e28f0303e770951158c73eaaa01e962a", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4cab72069210bc6e7a080cec9afffad1b33370149ed5d379b81c7c5f0c663fd4"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"}, + "yamerl": {:hex, :yamerl, "0.8.0", "8214cfe16bbabe5d1d6c14a14aea11c784b9a21903dd6a7c74f8ce180adae5c7", [:rebar3], [], "hexpm", "010634477bf9c208a0767dcca89116c2442cf0b5e87f9c870f85cd1c3e0c2aab"}, + "yaml_elixir": {:hex, :yaml_elixir, "2.4.0", "2f444abc3c994c902851fde56b6a9cb82895c291c05a0490a289035c2e62ae71", [:mix], [{:yamerl, "~> 0.7", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "4e25a6d5c873e393689c6f1062c5ec90f6cd1be2527b073178ae37eae4c78bee"}, } diff --git a/script/server b/script/server index 9b0c465b..66d85385 100755 --- a/script/server +++ b/script/server @@ -8,4 +8,4 @@ DIR_PATH=$(dirname $FULL_PATH) $DIR_PATH/update -mix phx.server \ No newline at end of file +iex -S mix phx.server