diff --git a/.travis.yml b/.travis.yml index d8763a4..cfc84a8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,11 +3,23 @@ elixir: - 1.5.0 - 1.6.0 otp_release: - - 19.0 - - 19.1 - 19.2 - 20.2 + +matrix: + include: + - elixir: 1.4 + otp_release: 19.2 env: - MIX_ENV=test + +cache: + directories: + - deps + - _build + script: + - mix compile --warning-as-errors + - if [ "$TRAVIS_ELIXIR_VERSION" == "1.6.0" ]; then mix format --check-formatted; fi + - mix credo - mix test diff --git a/CHANGELOG.md b/CHANGELOG.md index ca81ee0..df76a15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Ravenx Changelog +## v 2.0.0 + +* Strategies come in separate packages + ## v 1.1.3 * Format base code using Elixir 1.6 formatter diff --git a/README.md b/README.md index 33ce414..81077f1 100644 --- a/README.md +++ b/README.md @@ -31,19 +31,54 @@ end ## Strategies -We currently support Slack and E-mail notifications (but there are more to come!). +From version 2.0, strategies come in separate packages, so the dependencies +needed are not added by default. -Also, there is the possibility of creating 3rd party integrations that works with Ravenx, as mentioned bellow +To define strategies, just add their packages to your `mix.exs` file and add +them to Ravenx configuration as follows: + +```elixir +config :ravenx, + strategies: [ + email: Ravenx.Strategy.Email + slack: Ravenx.Strategy.Slack + my_strategy: MyApp.Ravenx.MyStrategy + ] +``` + +We currently maintain two strategies: + +* **Slack**: [hex.pm](https://hex.pm/packages/ravenx_slack) | [GitHub](https://github.com/acutario/ravenx_slack) +* **E-mail** (based on Bamboo): [hex.pm](https://hex.pm/packages/ravenx_email) | [GitHub](https://github.com/acutario/ravenx_email) + +Also, 3rd party strategies are supported and listed below. ### 3rd party strategies -Some amazing people created 3rd party strategies to use Ravenx with more services: +Amazing people created 3rd party strategies to use Ravenx with more services: * **Pusher** (thanks to [@behind-design](https://github.com/behind-design)): [hex.pm](https://hex.pm/packages/ravenx_pusher) | [GitHub](https://github.com/behind-design/ravenx-pusher) * **Telegram** (thanks to [@maratgaliev](https://github.com/maratgaliev)): [hex.pm](https://hex.pm/packages/ravenx_telegram) | [GitHub](https://github.com/maratgaliev/ravenx_telegram) Anyone can create a strategy that works with Ravenx, so if you have one, please let us know to add it to this list. +### Custom strategies + +Maybe there is some internal service you need to call to send notifications, so there is a way to create custom strategies for yout projects. + +First of all, you need to create a module that meet the [required behaviour](https://github.com/acutario/ravenx/blob/master/lib/ravenx/strategy_behaviour.ex), like the example you can see [here](https://github.com/acutario/ravenx/blob/master/lib/ravenx/strategy/dummy.ex). + +Then you can define custom strategies in application configuration: + +```elixir +config :ravenx, + strategies: [ + my_strategy: YourApp.MyStrategy + ] +``` + +and start using your strategy to deliver notifications using the atom assigned (in the example, `my_strategy`). + ## Single notification Sending a single notification is as simply as calling this method: @@ -170,19 +205,10 @@ Configuration can also be mixed by using the three methods: * Dynamic configuration common to more than one scenario using a configuration module. * Call-specific configuration sending a config Keyword list on `dispatch` method. -## Custom strategies - -Maybe there is some internal service you need to call to send notifications, so there is a way to create custom strategies for yout projects. - -First of all, you need to create a module that meet the [required behaviour](https://github.com/acutario/ravenx/blob/master/lib/ravenx/strategy_behaviour.ex), like the example you can see [here](https://github.com/acutario/ravenx/blob/master/lib/ravenx/strategy/dummy.ex). +## Contribute -Then you can define custom strategies in application configuration: +All contributions are welcome, and we really hope this repo will serve for beginners as well for more advanced developers. -```elixir -config :ravenx, - strategies: [ - my_strategy: YourApp.MyStrategy - ] -``` +If you have any doubt, feel free to ask, but always respecting our [Code of Conduct](https://github.com/acutario/ravenx_slack/blob/master/CODE_OF_CONDUCT.md). -and start using your strategy to deliver notifications using the atom assigned (in the example, `my_strategy`). +To contribute, create a fork of the repository, make your changes and create a PR. And remember, talking on PRs/issues is a must! diff --git a/lib/ravenx/strategy/email.ex b/lib/ravenx/strategy/email.ex deleted file mode 100644 index e9eb94b..0000000 --- a/lib/ravenx/strategy/email.ex +++ /dev/null @@ -1,141 +0,0 @@ -defmodule Ravenx.Strategy.Email do - @moduledoc """ - Ravenx Email strategy. - - Used to dispatch notifications via email. - """ - - @behaviour Ravenx.StrategyBehaviour - - alias Bamboo.Mailer - - @doc """ - Function used to send a notification via email. - - It receives two maps, containing the payload and options. - - The payload can include this keys: - - * `from`: (required) the email address from which the email is sent. - * `to`: (required) a list of email addresses that will receive the email. - * `cc`: a list of email addresses that will receive a copy of the email. - * `bcc`: a list of email addresses that will receive a hidden copy of the email. - * `subject`: the subject of the e-mail. - * `text_body`: the text version of the message. - * `html_body`: that HTML version of the message. - - In the options map there must be an `adapter` key indicating one of - the available adapters, and also the configuration required for each adapter. - - It will respond with a tuple, indicating if everything is `:ok` or there was - an `:error`. - - """ - @spec call(map, %{adapter: atom}) :: {:ok, Bamboo.Email.t()} | {:error, {atom, any}} - def call(payload, %{adapter: _a} = opts) do - %Bamboo.Email{} - |> parse_options(opts) - |> parse_payload(payload) - |> send_email(opts) - end - - def call(_payload, _opts), do: {:error, {:missing_config, :adapter}} - - # It returns a list of available adapters. - # - @spec available_adapters() :: keyword - def available_adapters do - default_adapters = [ - mailgun: Bamboo.MailgunAdapter, - mandrill: Bamboo.MandrillAdapter, - sendgrid: Bamboo.SendgridAdapter, - smtp: Bamboo.SMTPAdapter, - local: Bamboo.LocalAdapter, - test: Bamboo.TestAdapter - ] - - Keyword.merge(default_adapters, Application.get_env(:ravenx, :bamboo_adapters) || []) - end - - # Tries to get an adapter form list of available adapters - # - @spec available_adapter(atom) :: {:ok, atom} | {:error, nil} - defp available_adapter(adapter) do - case Keyword.get(available_adapters(), adapter, nil) do - nil -> - {:error, nil} - - adapter -> - {:ok, adapter} - end - end - - # Priate function to handle email sending and verify that required fields are - # passed - @spec send_email(Bamboo.Email.t(), map) :: {:ok, Bamboo.Email.t()} | {:error, {atom, any}} - - defp send_email(%Bamboo.Email{to: nil}, _opts), do: {:error, {:missing_config, :to}} - defp send_email(%Bamboo.Email{from: nil}, _opts), do: {:error, {:missing_config, :from}} - - defp send_email(%Bamboo.Email{} = email, opts) do - adapter = - opts - |> Map.get(:adapter) - |> available_adapter() - - case adapter do - {:ok, adapter} -> - try do - # We must tell the adapter to fulfill the options - complete_opts = adapter.handle_config(opts) - - response = Mailer.deliver_now(adapter, email, complete_opts) - {:ok, response} - rescue - e -> {:error, {:exception, e}} - end - - {:error, _} -> - {:error, {:adapter_not_found, adapter}} - end - end - - defp send_email(_email, _opts), do: {:error, {:unknown_error, nil}} - - # Private function to get information from payload and apply to the Bamboo - # email object. - # - @spec parse_payload(Bamboo.Email.t(), map()) :: Bamboo.Email.t() - defp parse_payload(email, payload) do - email - |> add_to_email(:subject, Map.get(payload, :subject)) - |> add_to_email(:from, Map.get(payload, :from)) - |> add_to_email(:to, Map.get(payload, :to)) - |> add_to_email(:cc, Map.get(payload, :cc)) - |> add_to_email(:bcc, Map.get(payload, :bcc)) - |> add_to_email(:text_body, Map.get(payload, :text_body)) - |> add_to_email(:html_body, Map.get(payload, :html_body)) - end - - # Private function to get information from options and apply to the Bamboo - # email object. - # - defp parse_options(email, options) do - email - |> add_to_email(:subject, Map.get(options, :subject)) - |> add_to_email(:from, Map.get(options, :from)) - |> add_to_email(:to, Map.get(options, :to)) - |> add_to_email(:cc, Map.get(options, :cc)) - |> add_to_email(:bcc, Map.get(options, :bcc)) - end - - # Private function to add information to the email object. - # - @spec add_to_email(Bamboo.Email.t(), atom, any) :: Bamboo.Email.t() - defp add_to_email(email, _key, nil), do: email - - defp add_to_email(email, key, value) do - email - |> Map.put(key, value) - end -end diff --git a/lib/ravenx/strategy/slack.ex b/lib/ravenx/strategy/slack.ex deleted file mode 100644 index 3d43cce..0000000 --- a/lib/ravenx/strategy/slack.ex +++ /dev/null @@ -1,84 +0,0 @@ -defmodule Ravenx.Strategy.Slack do - @moduledoc """ - Ravenx Slack strategy. - - Used to dispatch notifications to Slack service. - """ - - @behaviour Ravenx.StrategyBehaviour - - @doc """ - Function used to send a notification to Slack. - - The function receives a map including a `text` used to build the message, and an - `options` Mmp that can include this configuration: - - * `url`: URL of Slack integration to call. - * `username`: Username of the bot used to send the notification. - * `icon_emoji`: Icon to show as the bot avatar (with Slack format, like `:bird:`) - * `channel`: Channel or username to send the notification. - - It will respond with a tuple, indicating if everything was `:ok` or there was - an `:error`. - - """ - @spec call(map, map) :: {:ok, binary} | {:error, {atom, any}} - def call(payload, options \\ %{}) when is_map(payload) and is_map(options) do - url = Map.get(options, :url) - - payload - |> parse_options(options) - |> send_notification(url) - end - - # Private function to get options from Keyword received and apply it to the - # payload. - # - @spec parse_options(map, map) :: map - defp parse_options(payload, options) do - payload - |> add_to_payload(:username, Map.get(options, :username)) - |> add_to_payload(:icon_emoji, Map.get(options, :icon_emoji)) - |> add_to_payload(:channel, Map.get(options, :channel)) - end - - # Private function to send the notification using HTTPotion client. - # - @spec send_notification(map, binary) :: {:ok, binary} | {:error, {atom, any}} - defp send_notification(_payload, nil), do: {:error, {:missing_config, :url}} - - defp send_notification(payload, url) do - json_payload = Poison.encode!(payload) - - header = [ - {"Accept", "application/json"}, - {"Content-Type", "application/json"} - ] - - HTTPoison.start() - - case HTTPoison.post(url, json_payload, header) do - {:ok, %HTTPoison.Response{body: response, status_code: 200}} -> - {:ok, response} - - {:ok, %HTTPoison.Response{body: response}} -> - {:error, {:error_response, response}} - - {:error, %HTTPoison.Error{reason: reason}} -> - {:error, {:error, reason}} - - _ = e -> - {:error, {:unknown_response, e}} - end - end - - # Private function to add information to the payload. - # - @spec add_to_payload(map, atom, any) :: map - defp add_to_payload(payload, _key, nil), do: payload - - defp add_to_payload(payload, key, value) do - payload - |> Map.put(key, value) - end -end diff --git a/mix.exs b/mix.exs index 4b7fbfc..3c26461 100644 --- a/mix.exs +++ b/mix.exs @@ -4,8 +4,8 @@ defmodule Ravenx.Mixfile do def project do [ app: :ravenx, - version: "1.1.3", - elixir: "~> 1.3", + version: "2.0.0", + elixir: "~> 1.4", build_embedded: Mix.env() == :prod, start_permanent: Mix.env() == :prod, description: description(), @@ -22,10 +22,7 @@ defmodule Ravenx.Mixfile do # Type "mix help compile.app" for more information def application do [ - mod: {Ravenx, []}, - included_applications: [ - :httpoison - ] + mod: {Ravenx, []} ] end @@ -40,10 +37,6 @@ defmodule Ravenx.Mixfile do # Type "mix help deps" for more examples and options defp deps do [ - {:poison, "~> 2.0 or ~> 3.0"}, - {:httpoison, "~> 1.0"}, - {:bamboo, "~> 0.8"}, - {:bamboo_smtp, "~> 1.4.0"}, {:ex_doc, ">= 0.0.0", only: :dev}, {:dialyxir, "~> 0.4", only: :dev}, {:credo, ">= 0.8.0", only: [:dev, :test]} @@ -52,6 +45,7 @@ defmodule Ravenx.Mixfile do defp docs do [ + main: "readme", source_url: "https://github.com/acutario/ravenx", extras: ["README.md"] ]