diff --git a/config/config.exs b/config/config.exs index f235ca25..1520ac01 100644 --- a/config/config.exs +++ b/config/config.exs @@ -53,6 +53,12 @@ config :logger, :console, # Use Jason for JSON parsing in Phoenix config :phoenix, :json_library, Jason +config :bokken, Bokken.Scheduler, + jobs: [ + # Every midnight + {"@daily", {BirthdayNotifier, :notify_ninja_birthday, []}} + ] + # 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/lib/bokken/accounts.ex b/lib/bokken/accounts.ex index c5e8b4b7..6c7d0e2f 100644 --- a/lib/bokken/accounts.ex +++ b/lib/bokken/accounts.ex @@ -331,6 +331,7 @@ defmodule Bokken.Accounts do [%Ninja{}, ...] """ + def list_ninjas(preloads) when is_list(preloads) do Ninja |> Repo.all() @@ -510,6 +511,13 @@ defmodule Bokken.Accounts do end end + def get_guardian_email_by_ninja(ninja: user) do + user + |> Repo.preload(:ninja) + |> then(fn user -> get_guardian!(user.ninja.guardian_id, [:user]) end) + |> then(fn guardian -> guardian.user.email end) + end + alias Bokken.Accounts.Organizer @doc """ diff --git a/lib/bokken/application.ex b/lib/bokken/application.ex index ede80ace..77d3f440 100644 --- a/lib/bokken/application.ex +++ b/lib/bokken/application.ex @@ -15,9 +15,11 @@ defmodule Bokken.Application do # Start the PubSub system {Phoenix.PubSub, name: Bokken.PubSub}, # Start the Endpoint (http/https) - BokkenWeb.Endpoint + BokkenWeb.Endpoint, # Start a worker by calling: Bokken.Worker.start_link(arg) # {Bokken.Worker, arg} + # Operates cronjob tasks using :quantum + Bokken.Scheduler ] # See https://hexdocs.pm/elixir/Supervisor.html diff --git a/lib/bokken/birthday_notifier.ex b/lib/bokken/birthday_notifier.ex new file mode 100644 index 00000000..d7d54284 --- /dev/null +++ b/lib/bokken/birthday_notifier.ex @@ -0,0 +1,26 @@ +defmodule Bokken.BirthdayNotifier do + @moduledoc """ + Executes a task periodically to notify Ninjas on their birthday. + """ + + alias Bokken.Accounts + alias BokkenWeb.EventsEmails + import Bokken.Events.EventAdmin + + def send_happy_birthday_ninjas do + ninjas = Accounts.list_ninjas([:user]) + current_time = Date.utc_today() + + ninjas + |> Enum.filter(fn ninja -> + ninja.birthday.month == current_time.month && ninja.birthday.day == current_time.day && + not is_nil(ninja.user) + end) + |> Enum.map(fn ninja -> ninja.user end) + |> send_email(fn user -> + EventsEmails.ninja_birthday_email(user.ninja, + to: Accounts.get_guardian_email_by_ninja(ninja: user) + ) + end) + end +end diff --git a/lib/bokken/events/event_admin.ex b/lib/bokken/events/event_admin.ex index c6d138fa..9a4069ec 100644 --- a/lib/bokken/events/event_admin.ex +++ b/lib/bokken/events/event_admin.ex @@ -3,6 +3,7 @@ defmodule Bokken.Events.EventAdmin do The admin view config of events """ alias Bokken.{Events, Pairings} + alias Bokken.Mailer def list_actions(_conn) do [ @@ -46,4 +47,20 @@ defmodule Bokken.Events.EventAdmin do enrollments_close: nil ] end + + def send_email(users, email) do + users + |> Enum.reduce( + %{success: [], fail: []}, + fn user, accumulator -> + case Mailer.deliver(email.(user)) do + {:ok, _} -> + %{success: [user.email | accumulator[:success]], fail: accumulator[:fail]} + + {:error, _} -> + %{success: [accumulator[:success]], fail: [user.email | accumulator[:fail]]} + end + end + ) + end end diff --git a/lib/bokken/scheduler.ex b/lib/bokken/scheduler.ex new file mode 100644 index 00000000..b751df49 --- /dev/null +++ b/lib/bokken/scheduler.ex @@ -0,0 +1,6 @@ +defmodule Bokken.Scheduler do + @moduledoc """ + Standard module to run a cronjob. + """ + use Quantum, otp_app: :bokken +end diff --git a/lib/bokken_web/controllers/event_controller.ex b/lib/bokken_web/controllers/event_controller.ex index 7c7bb74b..da31e69c 100644 --- a/lib/bokken_web/controllers/event_controller.ex +++ b/lib/bokken_web/controllers/event_controller.ex @@ -4,8 +4,8 @@ defmodule BokkenWeb.EventController do alias Bokken.Accounts alias Bokken.Events alias Bokken.Events.Event - alias Bokken.Mailer alias BokkenWeb.EventsEmails + import Bokken.Events.EventAdmin action_fallback BokkenWeb.FallbackController @@ -143,20 +143,4 @@ defmodule BokkenWeb.EventController do |> render("emails.json", res) end end - - defp send_email(users, email) do - users - |> List.foldl( - %{success: [], fail: []}, - fn user, accumulator -> - case Mailer.deliver(email.(user)) do - {:ok, _} -> - %{success: [user.email | accumulator[:success]], fail: accumulator[:fail]} - - {:error, _} -> - %{success: [accumulator[:success]], fail: [user.email | accumulator[:fail]]} - end - end - ) - end end diff --git a/lib/bokken_web/emails/event_emails.ex b/lib/bokken_web/emails/event_emails.ex index 767c81bb..186dc091 100644 --- a/lib/bokken_web/emails/event_emails.ex +++ b/lib/bokken_web/emails/event_emails.ex @@ -46,6 +46,13 @@ defmodule BokkenWeb.EventsEmails do |> render_body(:mentor_event_reminder) end + def ninja_birthday_email(ninja, to: email) do + base_email(to: email) + |> subject("O CoderDojo Braga deseja-te um feliz aniversário! 🎂") + |> assign(:ninja, ninja) + |> render_body(:ninja_birthday) + end + defp base_email(to: email) do new() |> from({"CoderDojo Braga", "noreply@coderdojobraga.org"}) diff --git a/lib/bokken_web/templates/email/ninja_birthday.html.eex b/lib/bokken_web/templates/email/ninja_birthday.html.eex new file mode 100644 index 00000000..acc0b7a3 --- /dev/null +++ b/lib/bokken_web/templates/email/ninja_birthday.html.eex @@ -0,0 +1,72 @@ + + + + + + + diff --git a/lib/bokken_web/templates/email/ninja_birthday.text.eex b/lib/bokken_web/templates/email/ninja_birthday.text.eex new file mode 100644 index 00000000..8dbb7522 --- /dev/null +++ b/lib/bokken_web/templates/email/ninja_birthday.text.eex @@ -0,0 +1,3 @@ +Olá, <%= @ninja.first_name %>! 👋 + +O CoderDojo Braga deseja-te um feliz aniversário! 🎂 Esperamos por ti na próxima sessão! 😄 \ No newline at end of file diff --git a/mix.exs b/mix.exs index bc1b89fb..5ed6833e 100644 --- a/mix.exs +++ b/mix.exs @@ -68,6 +68,9 @@ defmodule Bokken.MixProject do {:plug_cowboy, "~> 2.5"}, {:cors_plug, "~> 3.0"}, + # cronjobs + {:quantum, "~> 3.0"}, + # utilities {:gettext, "~> 0.22.1"}, {:jason, "~> 1.3"}, diff --git a/mix.lock b/mix.lock index fb175b65..11bd9947 100644 --- a/mix.lock +++ b/mix.lock @@ -12,6 +12,7 @@ "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"}, "credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"}, + "crontab": {:hex, :crontab, "1.1.13", "3bad04f050b9f7f1c237809e42223999c150656a6b2afbbfef597d56df2144c5", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "d67441bec989640e3afb94e123f45a2bc42d76e02988c9613885dc3d01cf7085"}, "db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"}, "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, "dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"}, @@ -26,6 +27,7 @@ "expo": {:hex, :expo, "0.4.0", "bbe4bf455e2eb2ebd2f1e7d83530ce50fb9990eb88fc47855c515bfdf1c6626f", [:mix], [], "hexpm", "a8ed1683ec8b7c7fa53fd7a41b2c6935f539168a6bb0616d7fd6b58a36f3abf2"}, "faker": {:hex, :faker, "0.17.0", "671019d0652f63aefd8723b72167ecdb284baf7d47ad3a82a15e9b8a6df5d1fa", [:mix], [], "hexpm", "a7d4ad84a93fd25c5f5303510753789fc2433ff241bf3b4144d3f6f291658a6a"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "gen_stage": {:hex, :gen_stage, "1.2.0", "ee49244b57803f54bdab08a60a927e1b4e5bb5d635c52eca0f376a0673af3f8c", [:mix], [], "hexpm", "c3e40992c72e74d9c4eda16d7515bf32c9e7b634e827ab11091fff3290f7b503"}, "gettext": {:hex, :gettext, "0.22.1", "e7942988383c3d9eed4bdc22fc63e712b655ae94a672a27e4900e3d4a2c43581", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "ad105b8dab668ee3f90c0d3d94ba75e9aead27a62495c101d94f2657a190ac5d"}, "guardian": {:hex, :guardian, "2.3.1", "2b2d78dc399a7df182d739ddc0e566d88723299bfac20be36255e2d052fd215d", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bbe241f9ca1b09fad916ad42d6049d2600bbc688aba5b3c4a6c82592a54274c3"}, "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, @@ -54,12 +56,14 @@ "plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"}, "plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"}, "postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"}, + "quantum": {:hex, :quantum, "3.5.0", "8d2c5ba68c55991e8975aca368e3ab844ba01f4b87c4185a7403280e2c99cf34", [:mix], [{:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}, {:gen_stage, "~> 0.14 or ~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_registry, "~> 0.2", [hex: :telemetry_registry, repo: "hexpm", optional: false]}], "hexpm", "cab737d1d9779f43cb1d701f46dd05ea58146fd96238d91c9e0da662c1982bb6"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "swoosh": {:hex, :swoosh, "1.9.1", "0a5d7bf9954eb41d7e55525bc0940379982b090abbaef67cd8e1fd2ed7f8ca1a", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "76dffff3ffcab80f249d5937a592eaef7cc49ac6f4cdd27e622868326ed6371e"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, + "telemetry_registry": {:hex, :telemetry_registry, "0.3.0", "6768f151ea53fc0fbca70dbff5b20a8d663ee4e0c0b2ae589590e08658e76f1e", [:mix, :rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "492e2adbc609f3e79ece7f29fec363a97a2c484ac78a83098535d6564781e917"}, "timex": {:hex, :timex, "3.7.9", "790cdfc4acfce434e442f98c02ea6d84d0239073bfd668968f82ac63e9a6788d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "64691582e5bb87130f721fc709acfb70f24405833998fabf35be968984860ce1"}, "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},