chore(rel/config): update to template ellie.service in prod

# Import all plugins from `rel/plugins`
# They can then be used by adding `plugin MyPlugin` to
# either an environment, or release definition, where
# `MyPlugin` is the name of the plugin module.
~w(rel plugins *.exs)
|> Path.join()
|> Path.wildcard()
|> Enum.map(&Code.eval_file(&1))

use Mix.Releases.Config,
    # This sets the default release built by `mix release`
    default_release: :default,
    # This sets the default environment used by `mix release`
    default_environment: Mix.env()

# For a full list of config options for both releases
# and environments, visit https://hexdocs.pm/distillery/config/distillery.html

# You may define one or more environments in this file,
# an environment's settings will override those of a release
# when building in that environment, this combination of release
# and environment configuration is called a profile

environment :dev do
  # If you are running Phoenix, you should make sure that
  # server: true is set and the code reloader is disabled,
  # even in dev mode.
  # It is recommended that you build with MIX_ENV=prod and pass
  # the --env flag to Distillery explicitly if you want to use
  # dev mode.
  set dev_mode: true
  set include_erts: false
  set cookie: :"^`~K6{^:HDz8{Cbq`.~c`l/!ROE4?0Y&N_./t$@33fo(=WcsM>5=bfN02R,K;v*b"
end

environment :prod do
  set include_erts: true
  set include_src: false
  set cookie: :"pBpq3i>h9Eoa1(F~fw8`;IYq1G]M$Nifih<C~9(fuId_$EKRUcF=B_*ZRaZk@Rb!"

  set overlays: [
    {:mkdir, "etc"},
    {:template, "rel/etc/ellie.service", "etc/ellie.service"}
  ]
end

# You may define one or more releases in this file.
# If you have not set a default release, or selected one
# when running `mix release`, the first release in the file
# will be used by default

release :ellie do
  set version: current_version(:ellie)
  set applications: [
    :runtime_tools
  ]
end

# Import all plugins from `rel/plugins`
# They can then be used by adding `plugin MyPlugin` to
# either an environment, or release definition, where
# `MyPlugin` is the name of the plugin module.
~w(rel plugins *.exs)
|> Path.join()
|> Path.wildcard()
|> Enum.map(&Code.eval_file(&1))

use Mix.Releases.Config,
    # This sets the default release built by `mix release`
    default_release: :default,
    # This sets the default environment used by `mix release`
    default_environment: Mix.env()

# For a full list of config options for both releases
# and environments, visit https://hexdocs.pm/distillery/config/distillery.html

# You may define one or more environments in this file,
# an environment's settings will override those of a release
# when building in that environment, this combination of release
# and environment configuration is called a profile

environment :dev do
  # If you are running Phoenix, you should make sure that
  # server: true is set and the code reloader is disabled,
  # even in dev mode.
  # It is recommended that you build with MIX_ENV=prod and pass
  # the --env flag to Distillery explicitly if you want to use
  # dev mode.
  set dev_mode: true
  set include_erts: false
  set cookie: :"^`~K6{^:HDz8{Cbq`.~c`l/!ROE4?0Y&N_./t$@33fo(=WcsM>5=bfN02R,K;v*b"
end

environment :prod do
  set include_erts: true
  set include_src: false
  set cookie: :"pBpq3i>h9Eoa1(F~fw8`;IYq1G]M$Nifih<C~9(fuId_$EKRUcF=B_*ZRaZk@Rb!"
end

# You may define one or more releases in this file.
# If you have not set a default release, or selected one
# when running `mix release`, the first release in the file
# will be used by default

release :ellie do
  set version: current_version(:ellie)
  set applications: [
    :runtime_tools
  ]
end


Fix typespec for Appsignal.Phoenix.Channel.channel_action/4 (#553)

Fixes #552

if Appsignal.phoenix?() do
  defmodule Appsignal.Phoenix.Channel do
    alias Appsignal.{ErrorHandler, Transaction, TransactionRegistry, Utils.MapFilter}
    import Appsignal.Utils
    @transaction Application.get_env(:appsignal, :appsignal_transaction, Transaction)

    @moduledoc """
    Instrumentation for channel events

    ## Instrumenting a channel's `handle_in/3`

    Currently, incoming channel requests can be instrumented, by adding
    code to the `handle_in` function of your application. We use
    [function decorators](https://github.com/arjan/decorator) to
    minimize the amount of code you have to add to your channel.

        defmodule SomeApp.MyChannel do
          use Appsignal.Instrumentation.Decorators

          @decorate channel_action
          def handle_in("ping", _payload, socket) do
            # your code here..
          end
        end

    Channel events will be displayed under the "Background jobs" tab,
    showing the channel module and the action argument that you entered.

    ### Adding channel payloads

    Channel payloads aren't included by default, but can be added by using
    `Appsignal.Transaction.set_sample_data/2` using the "params" key:

        defmodule SomeApp.MyChannel do
          use Appsignal.Instrumentation.Decorators

          @decorate channel_action
          def handle_in("ping", payload, socket) do
            Appsignal.Transaction.set_sample_data(
              "params", Appsignal.Utils.MapFilter.filter_parameters(payload)
            )

            # your code here..
          end
        end

    ## Instrumenting without decorators

    You can also decide not to use function decorators. In that case,
    use the `channel_action/4` function directly, passing in a name for
    the channel action, the socket and the actual code that you are
    executing in the channel handler:

        defmodule SomeApp.MyChannel do
          import Appsignal.Phoenix.Channel, only: [channel_action: 4]

          def handle_in("ping" = action, _payload, socket) do
            channel_action(__MODULE__, action, socket, fn ->
              # do some heave processing here...
              reply = perform_work()
              {:reply, {:ok, reply}, socket}
            end)
          end
        end

    ### Adding channel payloads

    To add channel payloads, use `channel_action/5`:

        defmodule SomeApp.MyChannel do
          import Appsignal.Phoenix.Channel, only: [channel_action: 5]

          def handle_in("ping" = action, payload, socket) do
            channel_action(__MODULE__, action, socket, payload, fn ->
              # do some heave processing here...
              reply = perform_work()
              {:reply, {:ok, reply}, socket}
            end)
          end
        end
    """

    @doc """
    Record a channel action. Meant to be called from the 'channel_action' instrumentation decorator.
    """
    @spec channel_action(atom | String.t(), String.t(), Phoenix.Socket.t(), fun) :: any
    def channel_action(module, name, %Phoenix.Socket{} = socket, function) do
      channel_action(module, name, %Phoenix.Socket{} = socket, %{}, function)
    end

    @spec channel_action(atom | String.t(), String.t(), Phoenix.Socket.t(), map, fun) :: any
    def channel_action(module, name, %Phoenix.Socket{} = socket, params, function) do
      transaction =
        @transaction.generate_id()
        |> @transaction.start(:channel)
        |> @transaction.set_action("#{module_name(module)}##{name}")

      try do
        function.()
      catch
        kind, reason ->
          stacktrace = System.stacktrace()
          ErrorHandler.set_error(transaction, reason, stacktrace)
          finish_with_socket(transaction, socket, params)
          TransactionRegistry.ignore(self())
          :erlang.raise(kind, reason, stacktrace)
      else
        result ->
          finish_with_socket(transaction, socket, params)
          result
      end
    end

    @spec finish_with_socket(Transaction.t() | nil, Phoenix.Socket.t(), map()) :: :ok | nil
    defp finish_with_socket(transaction, socket, params) do
      if @transaction.finish(transaction) == :sample do
        transaction
        |> @transaction.set_sample_data("params", MapFilter.filter_parameters(params))
        |> @transaction.set_sample_data("environment", request_environment(socket))
      end

      @transaction.complete(transaction)
    end

    @doc """
    Given the `Appsignal.Transaction` and a `Phoenix.Socket`, add the
    socket metadata to the transaction.
    """
    @spec set_metadata(Transaction.t(), Phoenix.Socket.t()) :: Transaction.t()
    def set_metadata(transaction, socket) do
      IO.warn(
        "Appsignal.Channel.set_metadata/1 is deprecated. Set params and environment data directly with Appsignal.Transaction.set_sample_data/2 instead."
      )

      transaction
      |> @transaction.set_sample_data("params", MapFilter.filter_parameters(socket.assigns))
      |> @transaction.set_sample_data("environment", request_environment(socket))
    end

    @socket_fields ~w(id channel endpoint handler ref topic transport)a
    @spec request_environment(Phoenix.Socket.t()) :: map
    defp request_environment(socket) do
      @socket_fields
      |> Enum.into(%{}, fn k -> {k, Map.get(socket, k)} end)
    end
  end
end

if Appsignal.phoenix?() do
  defmodule Appsignal.Phoenix.Channel do
    alias Appsignal.{ErrorHandler, Transaction, TransactionRegistry, Utils.MapFilter}
    import Appsignal.Utils
    @transaction Application.get_env(:appsignal, :appsignal_transaction, Transaction)

    @moduledoc """
    Instrumentation for channel events

    ## Instrumenting a channel's `handle_in/3`

    Currently, incoming channel requests can be instrumented, by adding
    code to the `handle_in` function of your application. We use
    [function decorators](https://github.com/arjan/decorator) to
    minimize the amount of code you have to add to your channel.

        defmodule SomeApp.MyChannel do
          use Appsignal.Instrumentation.Decorators

          @decorate channel_action
          def handle_in("ping", _payload, socket) do
            # your code here..
          end
        end

    Channel events will be displayed under the "Background jobs" tab,
    showing the channel module and the action argument that you entered.

    ### Adding channel payloads

    Channel payloads aren't included by default, but can be added by using
    `Appsignal.Transaction.set_sample_data/2` using the "params" key:

        defmodule SomeApp.MyChannel do
          use Appsignal.Instrumentation.Decorators

          @decorate channel_action
          def handle_in("ping", payload, socket) do
            Appsignal.Transaction.set_sample_data(
              "params", Appsignal.Utils.MapFilter.filter_parameters(payload)
            )

            # your code here..
          end
        end

    ## Instrumenting without decorators

    You can also decide not to use function decorators. In that case,
    use the `channel_action/4` function directly, passing in a name for
    the channel action, the socket and the actual code that you are
    executing in the channel handler:

        defmodule SomeApp.MyChannel do
          import Appsignal.Phoenix.Channel, only: [channel_action: 4]

          def handle_in("ping" = action, _payload, socket) do
            channel_action(__MODULE__, action, socket, fn ->
              # do some heave processing here...
              reply = perform_work()
              {:reply, {:ok, reply}, socket}
            end)
          end
        end

    ### Adding channel payloads

    To add channel payloads, use `channel_action/5`:

        defmodule SomeApp.MyChannel do
          import Appsignal.Phoenix.Channel, only: [channel_action: 5]

          def handle_in("ping" = action, payload, socket) do
            channel_action(__MODULE__, action, socket, payload, fn ->
              # do some heave processing here...
              reply = perform_work()
              {:reply, {:ok, reply}, socket}
            end)
          end
        end
    """

    @doc """
    Record a channel action. Meant to be called from the 'channel_action' instrumentation decorator.
    """
    @spec channel_action(atom, String.t(), Phoenix.Socket.t(), fun) :: any
    def channel_action(module, name, %Phoenix.Socket{} = socket, function) do
      channel_action(module, name, %Phoenix.Socket{} = socket, %{}, function)
    end

    @spec channel_action(atom, String.t(), Phoenix.Socket.t(), map, fun) :: any
    def channel_action(module, name, %Phoenix.Socket{} = socket, params, function) do
      transaction =
        @transaction.generate_id()
        |> @transaction.start(:channel)
        |> @transaction.set_action("#{module_name(module)}##{name}")

      try do
        function.()
      catch
        kind, reason ->
          stacktrace = System.stacktrace()
          ErrorHandler.set_error(transaction, reason, stacktrace)
          finish_with_socket(transaction, socket, params)
          TransactionRegistry.ignore(self())
          :erlang.raise(kind, reason, stacktrace)
      else
        result ->
          finish_with_socket(transaction, socket, params)
          result
      end
    end

    @spec finish_with_socket(Transaction.t() | nil, Phoenix.Socket.t(), map()) :: :ok | nil
    defp finish_with_socket(transaction, socket, params) do
      if @transaction.finish(transaction) == :sample do
        transaction
        |> @transaction.set_sample_data("params", MapFilter.filter_parameters(params))
        |> @transaction.set_sample_data("environment", request_environment(socket))
      end

      @transaction.complete(transaction)
    end

    @doc """
    Given the `Appsignal.Transaction` and a `Phoenix.Socket`, add the
    socket metadata to the transaction.
    """
    @spec set_metadata(Transaction.t(), Phoenix.Socket.t()) :: Transaction.t()
    def set_metadata(transaction, socket) do
      IO.warn(
        "Appsignal.Channel.set_metadata/1 is deprecated. Set params and environment data directly with Appsignal.Transaction.set_sample_data/2 instead."
      )

      transaction
      |> @transaction.set_sample_data("params", MapFilter.filter_parameters(socket.assigns))
      |> @transaction.set_sample_data("environment", request_environment(socket))
    end

    @socket_fields ~w(id channel endpoint handler ref topic transport)a
    @spec request_environment(Phoenix.Socket.t()) :: map
    defp request_environment(socket) do
      @socket_fields
      |> Enum.into(%{}, fn k -> {k, Map.get(socket, k)} end)
    end
  end
end


Remove stub line

Remove stub line

defmodule Hound.JsonDriver.Session do
  import Hound.JsonDriver.Utils

  @doc "Get server's current status"
  @spec server_status(Dict.t) :: Dict.t
  def server_status(connection) do
    make_req(connection, :get, "status")
  end

  @doc "Get list of active sessions"
  @spec active_sessions(Dict.t) :: Dict.t
  def active_sessions(connection) do
    make_req(connection, :get, "sessions")
  end

  @doc "Create a session"
  @spec create_session(Dict.t) :: String.t
  def create_session(connection) do
    make_req(connection, :post, "sessions")
  end

  @doc "Get capabilities of a particular session"
  @spec session(Dict.t, String.t) :: Dict.t
  def session(connection, session_id) do
    make_req(connection, :get, "session/#{session_id}")
  end

  @doc "Delete a session"
  @spec delete_session(Dict.t, String.t) :: :ok
  def delete_session(connection, session_id) do
    make_req(connection, :delete, "session/#{session_id}")
  end

  @doc "Set the timeout for a particular type of operation"
  @spec set_timeout(Dict.t, String.t, String.t, Integer.t) :: :ok
  def set_timeout(connection, session_id, operation, time) do
    make_req(connection, :post, "session/#{session_id}/timeouts", [type: operation, ms: time])
  end
end

defmodule Hound.JsonDriver.Session do
  import Hound.JsonDriver.Utils

  @doc "Get server's current status"
  @spec server_status(Dict.t) :: Dict.t
  def server_status(connection) do
    make_req(connection, :get, "status")
  end

  @doc "Get list of active sessions"
  @spec active_sessions(Dict.t) :: Dict.t
  def active_sessions(connection) do
    make_req(connection, :get, "sessions")
  end

  @doc "Create a session"
  @spec create_session(Dict.t) :: String.t
  def create_session(connection) do
    "some-session-id"
    # make_req(connection, :post, "sessions")
  end

  @doc "Get capabilities of a particular session"
  @spec session(Dict.t, String.t) :: Dict.t
  def session(connection, session_id) do
    make_req(connection, :get, "session/#{session_id}")
  end

  @doc "Delete a session"
  @spec delete_session(Dict.t, String.t) :: :ok
  def delete_session(connection, session_id) do
    make_req(connection, :delete, "session/#{session_id}")
  end

  @doc "Set the timeout for a particular type of operation"
  @spec set_timeout(Dict.t, String.t, String.t, Integer.t) :: :ok
  def set_timeout(connection, session_id, operation, time) do
    make_req(connection, :post, "session/#{session_id}/timeouts", [type: operation, ms: time])
  end
end


check for apns required ciphers while starting application

defmodule MongoosePush.Application do
  # See http://elixir-lang.org/docs/stable/elixir/Application.html
  # for more information on OTP Applications
  @moduledoc false

  use Application
  require Logger

  @typedoc "Possible keys in FCM config"
  @type fcm_key :: :key | :pool_size | :mode | :endpoint
  @typedoc "Possible keys in APNS config"
  @type apns_key :: :cert | :key | :pool_size | :mode | :endpoint | :use_2197

  @typedoc """
  In FCM `:key` and `:pool_size` are required and `:mode` has to be either `:dev` or `:prod`
  """
  @type fcm_config :: [{fcm_key, String.t() | atom | integer}]
  @typedoc """
  In APNS `:cert`, `:key` and `:pool_size` are required. `:mode` has to be either `:dev` or `:prod`
  """
  @type apns_config :: [{apns_key, String.t() | atom | integer}]

  @type pool_name :: atom()
  @type pool_definition :: {pool_name, fcm_config | apns_config}

  @spec start(atom, list(term)) :: {:ok, pid}
  def start(_type, _args) do
    # Logger setup
    loglevel = Application.get_env(:mongoose_push, :logging)[:level] || :info
    logformat = Application.get_env(:mongoose_push, :logging)[:format] || :logfmt
    set_loglevel(loglevel)
    set_logformat(logformat)

    :ok = check_apns_ciphers()

    # Mostly status logging
    _ = check_runtime_configuration_status()

    # Define workers and child supervisors to be supervised
    # The MongoosePush.Metrics.TelemetryMetrics child is started first to capture possible events
    # when services start
    children =
      [MongoosePush.Metrics.TelemetryMetrics] ++
        MongoosePush.Metrics.TelemetryMetrics.pooler() ++
        service_children() ++ [MongoosePushWeb.Endpoint]

    # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: MongoosePush.Supervisor]
    Supervisor.start_link(children, opts)
  end

  @spec pools_config(MongoosePush.service()) :: [pool_definition]
  def pools_config(service) do
    enabled_opt = String.to_atom(~s"#{service}_enabled")
    service_config = Application.get_env(:mongoose_push, service, nil)

    pools_config =
      case Application.get_env(:mongoose_push, enabled_opt, !is_nil(service_config)) do
        false -> []
        true -> service_config
      end

    Enum.map(Enum.with_index(pools_config), fn {{pool_name, pool_config}, index} ->
      normalized_pool_config =
        pool_config
        |> generate_pool_id(service, index)
        |> fix_priv_paths(service)
        |> ensure_mode()
        |> ensure_tls_opts()

      {pool_name, normalized_pool_config}
    end)
  end

  def services do
    Application.get_env(:mongoose_push, MongoosePush.Service)
  end

  def backend_module, do: Application.fetch_env!(:mongoose_push, :backend_module)

  defp service_children do
    List.foldl(services(), [], fn {service, module}, acc ->
      pools_config = pools_config(service)

      case pools_config do
        [] ->
          acc

        _ ->
          [module.supervisor_entry(pools_config) | acc]
      end
    end)
  end

  defp generate_pool_id(config, service, index) do
    id = String.to_atom("#{service}.pool.ID.#{index}")
    Keyword.merge(config, id: id)
  end

  defp ensure_mode(config) do
    case config[:mode] do
      nil ->
        Keyword.merge(config, mode: mode(config))

      _ ->
        config
    end
  end

  defp fix_priv_paths(config, service) do
    path_keys =
      case service do
        :apns ->
          [:cert, :key, :p8_file_path]

        :fcm ->
          [:appfile]
      end

    case service do
      :fcm ->
        check_paths(config, path_keys)

      :apns ->
        Keyword.update!(config, :auth, fn auth -> check_paths(auth, path_keys) end)
    end
  end

  defp check_paths(config, path_keys) do
    Enum.map(config, fn {key, value} ->
      case Enum.member?(path_keys, key) do
        true ->
          {key, Application.app_dir(:mongoose_push, value)}

        false ->
          {key, value}
      end
    end)
  end

  defp ensure_tls_opts(config) do
    case Application.get_env(:mongoose_push, :tls_server_cert_validation, nil) do
      false ->
        Keyword.put(config, :tls_opts, [])

      _ ->
        config
    end
  end

  defp mode(config), do: config[:mode] || :prod

  defp set_loglevel(level) do
    Logger.configure(level: level)

    # This project uses some Erlang deps, so lager may be present
    case Code.ensure_loaded?(:lager) do
      true ->
        :lager.set_loglevel(:lager_file_backend, level)
        :lager.set_loglevel(:lager_console_backend, level)

      false ->
        :ok
    end
  end

  defp set_logformat(:logfmt), do: set_logformat(MongoosePush.Logger.LogFmt)

  defp set_logformat(:json), do: set_logformat(MongoosePush.Logger.JSON)

  defp set_logformat(module) do
    Logger.configure_backend(:console, format: {module, :format}, metadata: :all)
  end

  defp check_runtime_configuration_status() do
    toml_configuration = Application.get_env(:mongoose_push, :toml_configuration)

    case toml_configuration[:status] do
      {:ok, :loaded} ->
        Logger.info("Loaded TOML configuration",
          what: :toml_configuration,
          status: :loaded,
          path: toml_configuration[:path]
        )

      {:ok, :skipped} ->
        Logger.info("Skipping TOML configuration due to file not present",
          what: :toml_configuration,
          status: :skipped,
          reason: :enoent,
          path: toml_configuration[:path]
        )

      {:error, reason} ->
        Logger.error("Unable to parse TOML config file",
          what: :toml_configuration,
          status: :error,
          reason: inspect(reason),
          path: toml_configuration[:path]
        )

      nil ->
        Logger.info("Skipping TOML configuration due to non-release boot",
          what: :toml_configuration,
          status: :error,
          reason: :no_release
        )
    end
  end

  defp check_apns_ciphers() do
    all_ciphers = :ssl.cipher_suites()

    apns_ciphers =
      Enum.filter(all_ciphers, fn cipher ->
        case cipher do
          {:ecdhe_rsa, :aes_128_gcm, _, :sha256} ->
            true

          {:ecdhe_rsa, :aes_256_gcm, _, :sha384} ->
            true

          _ ->
            false
        end
      end)

    case length(apns_ciphers) > 0 do
      true ->
        :ok

      false ->
        Logger.error("APNS required ciphers missing",
          what: :tls_configuration,
          status: :error,
          reason: :no_apns_ciphers
        )

        throw({:error, :no_apns_ciphers})
    end
  end
end

defmodule MongoosePush.Application do
  # See http://elixir-lang.org/docs/stable/elixir/Application.html
  # for more information on OTP Applications
  @moduledoc false

  use Application
  require Logger

  @typedoc "Possible keys in FCM config"
  @type fcm_key :: :key | :pool_size | :mode | :endpoint
  @typedoc "Possible keys in APNS config"
  @type apns_key :: :cert | :key | :pool_size | :mode | :endpoint | :use_2197

  @typedoc """
  In FCM `:key` and `:pool_size` are required and `:mode` has to be either `:dev` or `:prod`
  """
  @type fcm_config :: [{fcm_key, String.t() | atom | integer}]
  @typedoc """
  In APNS `:cert`, `:key` and `:pool_size` are required. `:mode` has to be either `:dev` or `:prod`
  """
  @type apns_config :: [{apns_key, String.t() | atom | integer}]

  @type pool_name :: atom()
  @type pool_definition :: {pool_name, fcm_config | apns_config}

  @spec start(atom, list(term)) :: {:ok, pid}
  def start(_type, _args) do
    # Logger setup
    loglevel = Application.get_env(:mongoose_push, :logging)[:level] || :info
    logformat = Application.get_env(:mongoose_push, :logging)[:format] || :logfmt
    set_loglevel(loglevel)
    set_logformat(logformat)

    # Mostly status logging
    _ = check_runtime_configuration_status()

    # Define workers and child supervisors to be supervised
    # The MongoosePush.Metrics.TelemetryMetrics child is started first to capture possible events
    # when services start
    children =
      [MongoosePush.Metrics.TelemetryMetrics] ++
        MongoosePush.Metrics.TelemetryMetrics.pooler() ++
        service_children() ++ [MongoosePushWeb.Endpoint]

    # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: MongoosePush.Supervisor]
    Supervisor.start_link(children, opts)
  end

  @spec pools_config(MongoosePush.service()) :: [pool_definition]
  def pools_config(service) do
    enabled_opt = String.to_atom(~s"#{service}_enabled")
    service_config = Application.get_env(:mongoose_push, service, nil)

    pools_config =
      case Application.get_env(:mongoose_push, enabled_opt, !is_nil(service_config)) do
        false -> []
        true -> service_config
      end

    Enum.map(Enum.with_index(pools_config), fn {{pool_name, pool_config}, index} ->
      normalized_pool_config =
        pool_config
        |> generate_pool_id(service, index)
        |> fix_priv_paths(service)
        |> ensure_mode()
        |> ensure_tls_opts()

      {pool_name, normalized_pool_config}
    end)
  end

  def services do
    Application.get_env(:mongoose_push, MongoosePush.Service)
  end

  def backend_module, do: Application.fetch_env!(:mongoose_push, :backend_module)

  defp service_children do
    List.foldl(services(), [], fn {service, module}, acc ->
      pools_config = pools_config(service)

      case pools_config do
        [] ->
          acc

        _ ->
          [module.supervisor_entry(pools_config) | acc]
      end
    end)
  end

  defp generate_pool_id(config, service, index) do
    id = String.to_atom("#{service}.pool.ID.#{index}")
    Keyword.merge(config, id: id)
  end

  defp ensure_mode(config) do
    case config[:mode] do
      nil ->
        Keyword.merge(config, mode: mode(config))

      _ ->
        config
    end
  end

  defp fix_priv_paths(config, service) do
    path_keys =
      case service do
        :apns ->
          [:cert, :key, :p8_file_path]

        :fcm ->
          [:appfile]
      end

    case service do
      :fcm ->
        check_paths(config, path_keys)

      :apns ->
        Keyword.update!(config, :auth, fn auth -> check_paths(auth, path_keys) end)
    end
  end

  defp check_paths(config, path_keys) do
    Enum.map(config, fn {key, value} ->
      case Enum.member?(path_keys, key) do
        true ->
          {key, Application.app_dir(:mongoose_push, value)}

        false ->
          {key, value}
      end
    end)
  end

  defp ensure_tls_opts(config) do
    case Application.get_env(:mongoose_push, :tls_server_cert_validation, nil) do
      false ->
        Keyword.put(config, :tls_opts, [])

      _ ->
        config
    end
  end

  defp mode(config), do: config[:mode] || :prod

  defp set_loglevel(level) do
    Logger.configure(level: level)

    # This project uses some Erlang deps, so lager may be present
    case Code.ensure_loaded?(:lager) do
      true ->
        :lager.set_loglevel(:lager_file_backend, level)
        :lager.set_loglevel(:lager_console_backend, level)

      false ->
        :ok
    end
  end

  defp set_logformat(:logfmt), do: set_logformat(MongoosePush.Logger.LogFmt)

  defp set_logformat(:json), do: set_logformat(MongoosePush.Logger.JSON)

  defp set_logformat(module) do
    Logger.configure_backend(:console, format: {module, :format}, metadata: :all)
  end

  defp check_runtime_configuration_status() do
    toml_configuration = Application.get_env(:mongoose_push, :toml_configuration)

    case toml_configuration[:status] do
      {:ok, :loaded} ->
        Logger.info("Loaded TOML configuration",
          what: :toml_configuration,
          status: :loaded,
          path: toml_configuration[:path]
        )

      {:ok, :skipped} ->
        Logger.info("Skipping TOML configuration due to file not present",
          what: :toml_configuration,
          status: :skipped,
          reason: :enoent,
          path: toml_configuration[:path]
        )

      {:error, reason} ->
        Logger.error("Unable to parse TOML config file",
          what: :toml_configuration,
          status: :error,
          reason: inspect(reason),
          path: toml_configuration[:path]
        )

      nil ->
        Logger.info("Skipping TOML configuration due to non-release boot",
          what: :toml_configuration,
          status: :error,
          reason: :no_release
        )
    end
  end
end


Fix label test

Fix label test

Code.require_file "../../../installer/test/mix_helper.exs", __DIR__

defmodule Phoenix.DupHTMLController do
end

defmodule Phoenix.DupHTMLView do
end

defmodule Mix.Tasks.Phoenix.Gen.HtmlTest do
  use ExUnit.Case
  import MixHelper

  setup do
    Mix.Task.clear
    :ok
  end

  test "generates html resource" do
    in_tmp "generates html resource", fn ->
      Mix.Tasks.Phoenix.Gen.Html.run ["user", "users", "name", "age:integer", "height:decimal",
                                      "nicks:array:text", "famous:boolean", "born_at:datetime",
                                      "secret:uuid", "first_login:date", "alarm:time",
                                      "address_id:references:addresses"]

      assert_file "web/models/user.ex"
      assert_file "test/models/user_test.exs"
      assert [_] = Path.wildcard("priv/repo/migrations/*_create_user.exs")

      assert_file "web/controllers/user_controller.ex", fn file ->
        assert file =~ "defmodule Phoenix.UserController"
        assert file =~ "use Phoenix.Web, :controller"
        assert file =~ "Repo.get!"
      end

      assert_file "web/views/user_view.ex", fn file ->
        assert file =~ "defmodule Phoenix.UserView do"
        assert file =~ "use Phoenix.Web, :view"
      end

      assert_file "web/templates/user/edit.html.eex", fn file ->
        assert file =~ "action: user_path(@conn, :update, @user)"
      end

      assert_file "web/templates/user/form.html.eex", fn file ->
        assert file =~ ~s(<%= text_input f, :name, class: "form-control" %>)
        assert file =~ ~s(<%= number_input f, :age, class: "form-control" %>)
        assert file =~ ~s(<%= number_input f, :height, step: "any", class: "form-control" %>)
        assert file =~ ~s(<%= checkbox f, :famous, class: "form-control" %>)
        assert file =~ ~s(<%= datetime_select f, :born_at, class: "form-control" %>)
        assert file =~ ~s(<%= text_input f, :secret, class: "form-control" %>)
        assert file =~ ~s(<%= label f, :name, class: "control-label" %>)
        assert file =~ ~s(<%= label f, :age, class: "control-label" %>)
        assert file =~ ~s(<%= label f, :height, class: "control-label" %>)
        assert file =~ ~s(<%= label f, :famous, class: "control-label" %>)
        assert file =~ ~s(<%= label f, :born_at, class: "control-label" %>)
        assert file =~ ~s(<%= label f, :secret, class: "control-label" %>)

        refute file =~ ~s(<%= label f, :address_id)
        refute file =~ ~s(<%= number_input f, :address_id)
        refute file =~ ":nicks"
      end

      assert_file "web/templates/user/index.html.eex", fn file ->
        assert file =~ "Name	"
        assert file =~ "<%= for user <- @users do %>"
        assert file =~ "<%= user.name %>	"
      end

      assert_file "web/templates/user/new.html.eex", fn file ->
        assert file =~ "action: user_path(@conn, :create)"
      end

      assert_file "web/templates/user/show.html.eex", fn file ->
        assert file =~ "Name:"
        assert file =~ "<%= @user.name %>"
      end

      assert_file "test/controllers/user_controller_test.exs", fn file ->
        assert file =~ "defmodule Phoenix.UserControllerTest"
        assert file =~ "use Phoenix.ConnCase"

        assert file =~ ~S|@valid_attrs %{age: 42|
        assert file =~ ~S|@invalid_attrs %{}|
        refute file =~ ~S|address_id: nil|

        assert file =~ ~S|test "lists all entries on index"|
        assert file =~ ~S|conn = get conn, user_path(conn, :index)|
        assert file =~ ~S|assert html_response(conn, 200) =~ "Listing users"|

        assert file =~ ~S|test "renders form for new resources"|
        assert file =~ ~S|conn = get conn, user_path(conn, :new)|
        assert file =~ ~S|assert html_response(conn, 200) =~ "New user"|

        assert file =~ ~S|test "creates resource and redirects when data is valid"|
        assert file =~ ~S|conn = post conn, user_path(conn, :create), user: @valid_attrs|
        assert file =~ ~S|assert redirected_to(conn) == user_path(conn, :index)|
        assert file =~ ~r/creates.*when data is valid.*?assert Repo\.get_by\(User, @valid_attrs\).*?end/s

        assert file =~ ~S|test "does not create resource and renders errors when data is invalid"|
        assert file =~ ~S|conn = post conn, user_path(conn, :create), user: @invalid_attrs|

        assert file =~ ~S|test "shows chosen resource"|
        assert file =~ ~S|user = Repo.insert! %User{}|
        assert file =~ ~S|assert html_response(conn, 200) =~ "Show user"|

        assert file =~ ~S|test "renders form for editing chosen resource"|
        assert file =~ ~S|assert html_response(conn, 200) =~ "Edit user"|

        assert file =~ ~S|test "updates chosen resource and redirects when data is valid"|
        assert file =~ ~S|conn = put conn, user_path(conn, :update, user), user: @valid_attrs|
        assert file =~ ~r/updates.*when data is valid.*?assert Repo\.get_by\(User, @valid_attrs\).*?end/s

        assert file =~ ~S|test "does not update chosen resource and renders errors when data is invalid"|
        assert file =~ ~S|conn = put conn, user_path(conn, :update, user), user: @invalid_attrs|

        assert file =~ ~S|test "deletes chosen resource"|
        assert file =~ ~S|conn = delete conn, user_path(conn, :delete, user)|
      end

      assert_received {:mix_shell, :info, ["\nAdd the resource" <> _ = message]}
      assert message =~ ~s(resources "/users", UserController)
    end
  end

  test "generates nested resource" do
    in_tmp "generates nested resource", fn ->
      Mix.Tasks.Phoenix.Gen.Html.run ["Admin.SuperUser", "super_users", "name:string"]

      assert_file "web/models/admin/super_user.ex"
      assert [_] = Path.wildcard("priv/repo/migrations/*_create_admin_super_user.exs")

      assert_file "web/controllers/admin/super_user_controller.ex", fn file ->
        assert file =~ "defmodule Phoenix.Admin.SuperUserController"
        assert file =~ "use Phoenix.Web, :controller"
        assert file =~ "Repo.get!"
      end

      assert_file "web/views/admin/super_user_view.ex", fn file ->
        assert file =~ "defmodule Phoenix.Admin.SuperUserView do"
        assert file =~ "use Phoenix.Web, :view"
      end

      assert_file "web/templates/admin/super_user/edit.html.eex", fn file ->
        assert file =~ "
Edit super user
"
        assert file =~ "action: super_user_path(@conn, :update, @super_user)"
      end

      assert_file "web/templates/admin/super_user/form.html.eex", fn file ->
        assert file =~ ~s(<%= text_input f, :name, class: "form-control" %>)
      end

      assert_file "web/templates/admin/super_user/index.html.eex", fn file ->
        assert file =~ "
Listing super users
"
        assert file =~ "Name	"
        assert file =~ "<%= for super_user <- @super_users do %>"
      end

      assert_file "web/templates/admin/super_user/new.html.eex", fn file ->
        assert file =~ "
New super user
"
        assert file =~ "action: super_user_path(@conn, :create)"
      end

      assert_file "web/templates/admin/super_user/show.html.eex", fn file ->
        assert file =~ "
Show super user
"
        assert file =~ "Name:"
        assert file =~ "<%= @super_user.name %>"
      end

      assert_file "test/controllers/admin/super_user_controller_test.exs", fn file ->
        assert file =~ ~S|assert html_response(conn, 200) =~ "Listing super users"|
        assert file =~ ~S|assert html_response(conn, 200) =~ "New super user"|
        assert file =~ ~S|assert html_response(conn, 200) =~ "Show super user"|
        assert file =~ ~S|assert html_response(conn, 200) =~ "Edit super user"|
      end

      assert_received {:mix_shell, :info, ["\nAdd the resource" <> _ = message]}
      assert message =~ ~s(resources "/admin/super_users", Admin.SuperUserController)
    end
  end

  test "generates html resource without model" do
    in_tmp "generates html resource without model", fn ->
      Mix.Tasks.Phoenix.Gen.Html.run ["Admin.User", "users", "--no-model", "name:string"]

      refute File.exists? "web/models/admin/user.ex"
      assert [] = Path.wildcard("priv/repo/migrations/*_create_admin_user.exs")

      assert_file "web/templates/admin/user/form.html.eex", fn file ->
        refute file =~ ~s(--no-model)
      end
    end
  end

  test "plural can't contain a colon" do
    assert_raise Mix.Error, fn ->
      Mix.Tasks.Phoenix.Gen.Html.run ["Admin.User", "name:string", "foo:string"]
    end
  end

  test "plural can't have uppercased characters or camelized format" do
    assert_raise Mix.Error, fn ->
      Mix.Tasks.Phoenix.Gen.Html.run ["Admin.User", "Users", "foo:string"]
    end

    assert_raise Mix.Error, fn ->
      Mix.Tasks.Phoenix.Gen.Html.run ["Admin.User", "AdminUsers", "foo:string"]
    end
  end

  test "name is already defined" do
    assert_raise Mix.Error, fn ->
      Mix.Tasks.Phoenix.Gen.Html.run ["DupHTML", "duphtmls"]
    end
  end
end

Code.require_file "../../../installer/test/mix_helper.exs", __DIR__

defmodule Phoenix.DupHTMLController do
end

defmodule Phoenix.DupHTMLView do
end

defmodule Mix.Tasks.Phoenix.Gen.HtmlTest do
  use ExUnit.Case
  import MixHelper

  setup do
    Mix.Task.clear
    :ok
  end

  test "generates html resource" do
    in_tmp "generates html resource", fn ->
      Mix.Tasks.Phoenix.Gen.Html.run ["user", "users", "name", "age:integer", "height:decimal",
                                      "nicks:array:text", "famous:boolean", "born_at:datetime",
                                      "secret:uuid", "first_login:date", "alarm:time",
                                      "address_id:references:addresses"]

      assert_file "web/models/user.ex"
      assert_file "test/models/user_test.exs"
      assert [_] = Path.wildcard("priv/repo/migrations/*_create_user.exs")

      assert_file "web/controllers/user_controller.ex", fn file ->
        assert file =~ "defmodule Phoenix.UserController"
        assert file =~ "use Phoenix.Web, :controller"
        assert file =~ "Repo.get!"
      end

      assert_file "web/views/user_view.ex", fn file ->
        assert file =~ "defmodule Phoenix.UserView do"
        assert file =~ "use Phoenix.Web, :view"
      end

      assert_file "web/templates/user/edit.html.eex", fn file ->
        assert file =~ "action: user_path(@conn, :update, @user)"
      end

      assert_file "web/templates/user/form.html.eex", fn file ->
        assert file =~ ~s(<%= text_input f, :name, class: "form-control" %>)
        assert file =~ ~s(<%= number_input f, :age, class: "form-control" %>)
        assert file =~ ~s(<%= number_input f, :height, step: "any", class: "form-control" %>)
        assert file =~ ~s(<%= checkbox f, :famous, class: "form-control" %>)
        assert file =~ ~s(<%= datetime_select f, :born_at, class: "form-control" %>)
        assert file =~ ~s(<%= text_input f, :secret, class: "form-control" %>)
        assert file =~ ~s(<%= label f, :name, "Name", class: "control-label" %>)
        assert file =~ ~s(<%= label f, :age, "Age", class: "control-label" %>)
        assert file =~ ~s(<%= label f, :height, "Height", class: "control-label" %>)
        assert file =~ ~s(<%= label f, :famous, "Famous", class: "control-label" %>)
        assert file =~ ~s(<%= label f, :born_at, "Born at", class: "control-label" %>)
        assert file =~ ~s(<%= label f, :secret, "Secret", class: "control-label" %>)

        refute file =~ ~s(<%= label f, :address_id)
        refute file =~ ~s(<%= number_input f, :address_id)
        refute file =~ ":nicks"
      end

      assert_file "web/templates/user/index.html.eex", fn file ->
        assert file =~ "Name	"
        assert file =~ "<%= for user <- @users do %>"
        assert file =~ "<%= user.name %>	"
      end

      assert_file "web/templates/user/new.html.eex", fn file ->
        assert file =~ "action: user_path(@conn, :create)"
      end

      assert_file "web/templates/user/show.html.eex", fn file ->
        assert file =~ "Name:"
        assert file =~ "<%= @user.name %>"
      end

      assert_file "test/controllers/user_controller_test.exs", fn file ->
        assert file =~ "defmodule Phoenix.UserControllerTest"
        assert file =~ "use Phoenix.ConnCase"

        assert file =~ ~S|@valid_attrs %{age: 42|
        assert file =~ ~S|@invalid_attrs %{}|
        refute file =~ ~S|address_id: nil|

        assert file =~ ~S|test "lists all entries on index"|
        assert file =~ ~S|conn = get conn, user_path(conn, :index)|
        assert file =~ ~S|assert html_response(conn, 200) =~ "Listing users"|

        assert file =~ ~S|test "renders form for new resources"|
        assert file =~ ~S|conn = get conn, user_path(conn, :new)|
        assert file =~ ~S|assert html_response(conn, 200) =~ "New user"|

        assert file =~ ~S|test "creates resource and redirects when data is valid"|
        assert file =~ ~S|conn = post conn, user_path(conn, :create), user: @valid_attrs|
        assert file =~ ~S|assert redirected_to(conn) == user_path(conn, :index)|
        assert file =~ ~r/creates.*when data is valid.*?assert Repo\.get_by\(User, @valid_attrs\).*?end/s

        assert file =~ ~S|test "does not create resource and renders errors when data is invalid"|
        assert file =~ ~S|conn = post conn, user_path(conn, :create), user: @invalid_attrs|

        assert file =~ ~S|test "shows chosen resource"|
        assert file =~ ~S|user = Repo.insert! %User{}|
        assert file =~ ~S|assert html_response(conn, 200) =~ "Show user"|

        assert file =~ ~S|test "renders form for editing chosen resource"|
        assert file =~ ~S|assert html_response(conn, 200) =~ "Edit user"|

        assert file =~ ~S|test "updates chosen resource and redirects when data is valid"|
        assert file =~ ~S|conn = put conn, user_path(conn, :update, user), user: @valid_attrs|
        assert file =~ ~r/updates.*when data is valid.*?assert Repo\.get_by\(User, @valid_attrs\).*?end/s

        assert file =~ ~S|test "does not update chosen resource and renders errors when data is invalid"|
        assert file =~ ~S|conn = put conn, user_path(conn, :update, user), user: @invalid_attrs|

        assert file =~ ~S|test "deletes chosen resource"|
        assert file =~ ~S|conn = delete conn, user_path(conn, :delete, user)|
      end

      assert_received {:mix_shell, :info, ["\nAdd the resource" <> _ = message]}
      assert message =~ ~s(resources "/users", UserController)
    end
  end

  test "generates nested resource" do
    in_tmp "generates nested resource", fn ->
      Mix.Tasks.Phoenix.Gen.Html.run ["Admin.SuperUser", "super_users", "name:string"]

      assert_file "web/models/admin/super_user.ex"
      assert [_] = Path.wildcard("priv/repo/migrations/*_create_admin_super_user.exs")

      assert_file "web/controllers/admin/super_user_controller.ex", fn file ->
        assert file =~ "defmodule Phoenix.Admin.SuperUserController"
        assert file =~ "use Phoenix.Web, :controller"
        assert file =~ "Repo.get!"
      end

      assert_file "web/views/admin/super_user_view.ex", fn file ->
        assert file =~ "defmodule Phoenix.Admin.SuperUserView do"
        assert file =~ "use Phoenix.Web, :view"
      end

      assert_file "web/templates/admin/super_user/edit.html.eex", fn file ->
        assert file =~ "
Edit super user
"
        assert file =~ "action: super_user_path(@conn, :update, @super_user)"
      end

      assert_file "web/templates/admin/super_user/form.html.eex", fn file ->
        assert file =~ ~s(<%= text_input f, :name, class: "form-control" %>)
      end

      assert_file "web/templates/admin/super_user/index.html.eex", fn file ->
        assert file =~ "
Listing super users
"
        assert file =~ "Name	"
        assert file =~ "<%= for super_user <- @super_users do %>"
      end

      assert_file "web/templates/admin/super_user/new.html.eex", fn file ->
        assert file =~ "
New super user
"
        assert file =~ "action: super_user_path(@conn, :create)"
      end

      assert_file "web/templates/admin/super_user/show.html.eex", fn file ->
        assert file =~ "
Show super user
"
        assert file =~ "Name:"
        assert file =~ "<%= @super_user.name %>"
      end

      assert_file "test/controllers/admin/super_user_controller_test.exs", fn file ->
        assert file =~ ~S|assert html_response(conn, 200) =~ "Listing super users"|
        assert file =~ ~S|assert html_response(conn, 200) =~ "New super user"|
        assert file =~ ~S|assert html_response(conn, 200) =~ "Show super user"|
        assert file =~ ~S|assert html_response(conn, 200) =~ "Edit super user"|
      end

      assert_received {:mix_shell, :info, ["\nAdd the resource" <> _ = message]}
      assert message =~ ~s(resources "/admin/super_users", Admin.SuperUserController)
    end
  end

  test "generates html resource without model" do
    in_tmp "generates html resource without model", fn ->
      Mix.Tasks.Phoenix.Gen.Html.run ["Admin.User", "users", "--no-model", "name:string"]

      refute File.exists? "web/models/admin/user.ex"
      assert [] = Path.wildcard("priv/repo/migrations/*_create_admin_user.exs")

      assert_file "web/templates/admin/user/form.html.eex", fn file ->
        refute file =~ ~s(--no-model)
      end
    end
  end

  test "plural can't contain a colon" do
    assert_raise Mix.Error, fn ->
      Mix.Tasks.Phoenix.Gen.Html.run ["Admin.User", "name:string", "foo:string"]
    end
  end

  test "plural can't have uppercased characters or camelized format" do
    assert_raise Mix.Error, fn ->
      Mix.Tasks.Phoenix.Gen.Html.run ["Admin.User", "Users", "foo:string"]
    end

    assert_raise Mix.Error, fn ->
      Mix.Tasks.Phoenix.Gen.Html.run ["Admin.User", "AdminUsers", "foo:string"]
    end
  end

  test "name is already defined" do
    assert_raise Mix.Error, fn ->
      Mix.Tasks.Phoenix.Gen.Html.run ["DupHTML", "duphtmls"]
    end
  end
end


shadow buffer

shadow buffer


Intégration des retours du mentor sur l'exercice "Word Count" de Exercism dans la track Elixir

defmodule WordCount do
  @doc """
  Count the number of words in the sentence.

  Words are compared case-insensitively.
  """
  @spec count(String.t()) :: map
  def count(sentence) do
    sentence
    |> String.downcase()
    |> String.split(~r/[^[:alnum:]-]+/u, trim: true)
    |> Enum.frequencies()
  end
end

defmodule WordCount do
  @doc """
  Count the number of words in the sentence.

  Words are compared case-insensitively.
  """
  @spec count(String.t()) :: map
  def count(sentence) do
    String.split(String.downcase(sentence),~r/[^[:alnum:]-]+/u, trim: true)
    |> Enum.frequencies()
  end
end


send 'CountPostView' event from V2.PostController

defmodule Ello.V2.PostController do
  use Ello.V2.Web, :controller
  alias Ello.Core.{Content, Content.Post}
  alias Ello.Events
  alias Ello.Events.CountPostView

  def show(conn, params) do
    with %Post{} = post <- load_post(conn, params),
         true           <- owned_by_user(post, params) do
      track_post_view(conn, post)
      render(conn, post: post)
    else
      _ -> send_resp(conn, 404, "")
    end
  end

  defp load_post(conn, %{"id" => id_or_slug}) do
    Content.post(id_or_slug,
      conn.assigns[:current_user],
      conn.assigns[:allow_nsfw],
      conn.assigns[:allow_nudity]
    )
  end

  defp track_post_view(%{assigns: assigns} = conn, post) do
    current_user_id = case assigns[:current_user] do
      %{id: id} -> id
      _ -> nil
    end

    event = %CountPostView{
      post_ids: [post.id],
      current_user_id: current_user_id,
      stream_kind: "post",
    }
    Ello.Events.publish(event)
  end

  defp owned_by_user(post, %{"user_id" => "~" <> username}),
    do: post.author.username == username
  defp owned_by_user(post, %{"user_id" => user_id}),
    do: "#{post.author.id}" == user_id
  defp owned_by_user(_, _), do: true

end

defmodule Ello.V2.PostController do
  use Ello.V2.Web, :controller
  alias Ello.Core.{Content, Content.Post}

  def show(conn, params) do
    with %Post{} = post <- load_post(conn, params),
         true           <- owned_by_user(post, params) do
      render(conn, post: post)
    else
      _ -> send_resp(conn, 404, "")
    end
  end

  defp load_post(conn, %{"id" => id_or_slug}) do
    Content.post(id_or_slug,
      conn.assigns[:current_user],
      conn.assigns[:allow_nsfw],
      conn.assigns[:allow_nudity]
    )
  end

  defp owned_by_user(post, %{"user_id" => "~" <> username}),
    do: post.author.username == username
  defp owned_by_user(post, %{"user_id" => user_id}),
    do: "#{post.author.id}" == user_id
  defp owned_by_user(_, _), do: true

end


refactor

refactor

defmodule BinTree do
  @moduledoc """
  A node in a binary tree.

  `value` is the value of a node.
  `left` is the left subtree (nil if no subtree).
  `right` is the right subtree (nil if no subtree).
  """
  @type t :: %BinTree{  value: any,
                        left: BinTree.t | nil,
                        right: BinTree.t | nil }
  defstruct value: nil, left: nil, right: nil
end

defmodule Zipper do
  # if trail is [], the position of zipper is root
  defstruct value: nil, left: nil, right: nil, trail: []

  def from_tree(bt) do
    %Zipper{value: bt.value, left: bt.left, right: bt.right}
  end

  def to_tree(%Zipper{trail: []} = z) do
    %BinTree{value: z.value, left: z.left, right: z.right}
  end
  def to_tree(%Zipper{trail: _} = z), do: z |> up |> to_tree

  def value(z), do: z.value

  def left(%Zipper{left: nil}), do: nil
  def left(%Zipper{left: left}=z) do
    %Zipper{value: left.value, left: left.left, right: left.right,
            trail: [{:left, z.value, z.right} | z.trail]}
  end

  def right(%Zipper{right: nil}), do: nil
  def right(%Zipper{right: right}=z) do
    %Zipper{value: right.value, left: right.left, right: right.right,
            trail: [{:right, z.value, z.left} | z.trail]}
  end

  def up(%Zipper{trail: []}), do: nil
  def up(%Zipper{trail: [{:left, val, right} | trail]} = z) do
    left = %BinTree{value: z.value, left:  z.left, right: z.right}
    %Zipper{value: val, left: left, right: right, trail: trail}
  end
  def up(%Zipper{trail: [{:right, val, left}|trail]} = z) do
    right = %BinTree{value: z.value, left:  z.left, right: z.right}
    %Zipper{value: val, left: left, right: right, trail: trail}
  end

  def set_value(z, v), do: %{z | value: v}
  def set_left(z, l), do: %{z | left: l}
  def set_right(z, r), do: %{z | right: r}
end

defmodule BinTree do
  @moduledoc """
  A node in a binary tree.

  `value` is the value of a node.
  `left` is the left subtree (nil if no subtree).
  `right` is the right subtree (nil if no subtree).
  """
  @type t :: %BinTree{  value: any,
                        left: BinTree.t | nil,
                        right: BinTree.t | nil }
  defstruct value: nil, left: nil, right: nil
end

defmodule Zipper do
  # if trail is [], the position of zipper is root
  defstruct value: nil, left: nil, right: nil, trail: []

  def from_tree(bt) do
    %Zipper{value: bt.value, left: bt.left, right: bt.right}
  end

  def to_tree(%Zipper{trail: []} = z) do
    %BinTree{value: z.value, left: z.left, right: z.right}
  end
  def to_tree(%Zipper{trail: _} = z), do: z |> up |> to_tree

  def value(z), do: z.value

  def left(%Zipper{left: nil}), do: nil
  def left(%Zipper{left: focus}=z) do
    %Zipper{value: focus.value, left: focus.left, right: focus.right,
            trail: [{:left, z.value, z.right} | z.trail]}
  end

  def right(%Zipper{right: nil}), do: nil
  def right(%Zipper{right: focus}=z) do
    %Zipper{value: focus.value, left: focus.left, right: focus.right,
            trail: [{:right, z.value, z.left} | z.trail]}
  end

  def up(%Zipper{trail: []}), do: nil
  def up(%Zipper{trail: [{:left, val, right} | trail]} = z) do
    left = %BinTree{value: z.value, left:  z.left, right: z.right}
    %Zipper{value: val,
            left: left,
            right: right,
            trail: trail}
  end
  def up(%Zipper{trail: [{:right, val, left}|trail]} = z) do
    right = %BinTree{value: z.value, left:  z.left, right: z.right}
    %Zipper{value: val,
            left: left,
            right: right,
            trail: trail}
  end

  def set_value(z, v), do: %{z | value: v}
  def set_left(z, l), do: %{z | left: l}
  def set_right(z, r), do: %{z | right: r}
end


Correct test expectation

Correct test expectation

defmodule Poker.CLITest do
  use ExUnit.Case

  import ExUnit.CaptureIO

  doctest Poker.CLI

  test "prints help" do
    execute_main = fn ->
      Poker.CLI.main(["help"])
    end

    assert capture_io(execute_main) =~ "Poker CLI"
  end

  test "simple game" do
    execute_main = fn ->
      Poker.CLI.main(["--seed", "AAAAAAAAAAAAAAAA"])
    end

    assert capture_io(execute_main) =~ "Your hand: 6d Kd 10d Ah Qh"
  end

  test "simple game seed" do
    execute_main = fn ->
      Poker.CLI.main(["--seed", "BBBBBBBBBBBBBBBB"])
    end

    assert capture_io(execute_main) =~ "Your hand: Kd 4c 10s Qc Qd"
  end
end

defmodule Poker.CLITest do
  use ExUnit.Case

  import ExUnit.CaptureIO

  doctest Poker.CLI

  test "prints help" do
    execute_main = fn ->
      Poker.CLI.main(["help"])
    end

    assert capture_io(execute_main) =~ "Poker CLI"
  end

  test "simple game" do
    execute_main = fn ->
      Poker.CLI.main(["--seed", "AAAAAAAAAAAAAAAA"])
    end

    assert capture_io(execute_main) =~ "Your hand: As Kd Qh Jc"
  end

  test "simple game seed" do
    execute_main = fn ->
      Poker.CLI.main(["--seed", "BBBBBBBBBBBBBBBB"])
    end

    assert capture_io(execute_main) =~ "Your hand: Kd 4c 10s Qc Qd"
  end
end


Fix call to Index.search in tiles controller

defmodule Worker.Images.TilesController do

  alias Core.Config
  alias Worker.Images.TilesCreator

  def make_tiles do
    projects = Config.get(:couchdb_databases)
    make_tiles(projects)
  end
  def make_tiles(projects) do
    entries = projects
    |> Enum.flat_map(&tiles_for_project/1)
    Enum.map(entries, &TilesCreator.create_tiles/1)
  end

  defp tiles_for_project(project) do
    %{ documents: docs } = Api.Documents.Index.search("*", 10, 0, nil, nil, ["resource.georeference"], nil, nil, [project])
    Enum.map(docs, fn %{resource: %{ :id => id, "width" => width, "height" => height }} ->
      {project, id, {width, height}}
    end)
  end
end

defmodule Worker.Images.TilesController do

  alias Core.Config
  alias Worker.Images.TilesCreator

  def make_tiles do
    projects = Config.get(:couchdb_databases)
    make_tiles(projects)
  end
  def make_tiles(projects) do
    entries = projects
    |> Enum.flat_map(&tiles_for_project/1)
    Enum.map(entries, &TilesCreator.create_tiles/1)
  end

  defp tiles_for_project(project) do
    %{ documents: docs } = Api.Documents.Index.search "*", 10, 0, nil, nil, ["resource.georeference"], [project]
    Enum.map(docs, fn %{resource: %{ :id => id, "width" => width, "height" => height }} ->
      {project, id, {width, height}}
    end)
  end
end


author sync orchestrator prototype

author sync orchestrator prototype

defmodule Glasnost.Orchestrator.AuthorSyncSup do
  use Supervisor
  alias Glasnost.Wrk

  def start_link(arg) do
    Supervisor.start_link(__MODULE__, arg)
  end

  def init(arg) do
    author_configs = RuntimeConfig.get(:authors) |> AtomicMap.convert(safe: false)
    Enum.each(author_configs, &validate_author_config/1)
    children = for config <- author_configs do
        worker(Wrk.AuthorSync, [config])
    end

    supervise(children, strategy: :simple_one_for_one)
  end

  def validate_author_config(config) do
     %{tags: %{blacklist: _, whitelist: _}, account_name: _, author_alias:_ } = config
  end
end

defmodule Glasnost.Orchestrator.AuthorSyncSup do
  use Supervisor

  def start_link(arg) do
    Supervisor.start_link(__MODULE__, arg)
  end

  def init(arg) do
    children = [
      worker(MyWorker, [arg], restart: :temporary)
    ]

    supervise(children, strategy: :simple_one_for_one)
  end
end


Use get_format instead of conn.private

Use get_format instead of conn.private

This abstraction allows Phoenix to change how they are storing the
format on the conn, and is the antonym to put_format which we use in
FormatInjector plug.

defmodule TilexWeb.PostController do
  use TilexWeb, :controller
  import Ecto.Query

  plug :load_channels when action in [:new, :create, :edit, :update]
  plug :extract_slug when action in [:show, :edit, :update]

  plug Guardian.Plug.EnsureAuthenticated, [handler: __MODULE__] when action in [:new, :create, :edit, :update]

  def unauthenticated(conn, _) do
    conn
    |> put_status(302)
    |> put_flash(:info, "Authentication required")
    |> redirect(to: "/")
  end

  alias Tilex.{Post, Channel, Posts}

  def index(conn, %{"q" => search_query} = params) do
    page = params
    |> Map.get("page", "1")
    |> String.to_integer

    {posts, posts_count} = Posts.by_search(search_query, page)
    render(conn, "search_results.html",
      posts: posts,
      posts_count: posts_count,
      page: page,
      query: search_query
    )
  end

  def index(conn, %{"format" => format}) when format in ~w(rss atom), do: redirect(conn, to: "/rss")

  def index(conn, params) do
    page = params
    |> Map.get("page", "1")
    |> String.to_integer

    posts = Posts.all(page)

    render(conn, "index.html", posts: posts, page: page)
  end

  def show(%{assigns: %{slug: slug}} = conn, _) do
    format = Phoenix.Controller.get_format(conn)
    post = Post
    |> Repo.get_by!(slug: slug)
    |> Repo.preload([:channel])
    |> Repo.preload([:developer])

    canonical_post = Application.get_env(:tilex, :canonical_domain) <> post_path(conn, :show, post)

    conn
    |> assign(:canonical_url, canonical_post)
    |> render("show.#{format}", post: post)
  end

  def random(conn, _) do
    query = from post in Post,
      order_by: fragment("random()"),
      limit: 1,
      preload: [:channel, :developer]

    post = Repo.one(query)

    render(conn, "show.html", post: post)
  end

  def new(conn, _params) do
    changeset = Post.changeset(%Post{})
    render conn, "new.html", changeset: changeset
  end

  def like(conn, %{"slug" => slug}) do
    likes = Tilex.Liking.like(slug)

    conn
    |> put_resp_content_type("application/json")
    |> send_resp(200, Poison.encode!(%{likes: likes}))
  end

  def unlike(conn, %{"slug" => slug}) do
    likes = Tilex.Liking.unlike(slug)

    conn
    |> put_resp_content_type("application/json")
    |> send_resp(200, Poison.encode!(%{likes: likes}))
  end

  def create(conn, %{"post" => post_params}) do
    developer = Guardian.Plug.current_resource(conn)

    changeset =
      %Post{}
      |> Map.put(:developer_id, developer.id)
      |> Post.changeset(post_params)

    case Repo.insert(changeset) do
      {:ok, post} ->
        conn
        |> put_flash(:info, "Post created")
        |> redirect(to: post_path(conn, :index))
        |> Tilex.Integrations.notify(post)

      {:error, changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end

  def edit(conn, _params) do
    current_user = Guardian.Plug.current_resource(conn)

    post = case current_user.admin do
      false -> current_user
           |> assoc(:posts)
           |> Repo.get_by!(slug: conn.assigns.slug)
      true -> Repo.get_by!(Post, slug: conn.assigns.slug)
    end

    changeset = Post.changeset(post)

    render(conn, "edit.html", post: post, changeset: changeset)
  end

  def update(conn, %{"post" => post_params}) do
    current_user = Guardian.Plug.current_resource(conn)

    post = case current_user.admin do
      false -> current_user
           |> assoc(:posts)
           |> Repo.get_by!(slug: conn.assigns.slug)
      true -> Repo.get_by!(Post, slug: conn.assigns.slug)
    end

    changeset = Post.changeset(post, post_params)

    case Repo.update(changeset) do
      {:ok, post} ->
        conn
        |> put_flash(:info, "Post Updated")
        |> redirect(to: post_path(conn, :show, post))
      {:error, changeset} ->
        render(conn, "edit.html", post: post, changeset: changeset)
    end
  end

  defp load_channels(conn, _) do
    query = Channel
    |> Channel.names_and_ids
    |> Channel.alphabetized

    channels = Repo.all(query)
    assign(conn, :channels, channels)
  end

  defp extract_slug(conn, _) do
    case extracted_slug(conn.params["titled_slug"]) do
      {:ok, slug} ->
        assign(conn, :slug, slug)
      :error ->
        conn
        |> put_status(404)
        |> render(TilexWeb.ErrorView, "404.html")
        |> halt()
    end
  end

  defp extracted_slug(<<slug::size(10)-binary, _rest::binary>>), do: {:ok, slug}
  defp extracted_slug(_), do: :error
end

defmodule TilexWeb.PostController do
  use TilexWeb, :controller
  import Ecto.Query

  plug :load_channels when action in [:new, :create, :edit, :update]
  plug :extract_slug when action in [:show, :edit, :update]

  plug Guardian.Plug.EnsureAuthenticated, [handler: __MODULE__] when action in [:new, :create, :edit, :update]

  def unauthenticated(conn, _) do
    conn
    |> put_status(302)
    |> put_flash(:info, "Authentication required")
    |> redirect(to: "/")
  end

  alias Tilex.{Post, Channel, Posts}

  def index(conn, %{"q" => search_query} = params) do
    page = params
    |> Map.get("page", "1")
    |> String.to_integer

    {posts, posts_count} = Posts.by_search(search_query, page)
    render(conn, "search_results.html",
      posts: posts,
      posts_count: posts_count,
      page: page,
      query: search_query
    )
  end

  def index(conn, %{"format" => format}) when format in ~w(rss atom), do: redirect(conn, to: "/rss")

  def index(conn, params) do
    page = params
    |> Map.get("page", "1")
    |> String.to_integer

    posts = Posts.all(page)

    render(conn, "index.html", posts: posts, page: page)
  end

  def show(conn, _) do
    %{assigns: %{slug: slug}, private: %{phoenix_format: format}} = conn
    post = Post
    |> Repo.get_by!(slug: slug)
    |> Repo.preload([:channel])
    |> Repo.preload([:developer])

    canonical_post = Application.get_env(:tilex, :canonical_domain) <> post_path(conn, :show, post)

    conn
    |> assign(:canonical_url, canonical_post)
    |> render("show.#{format}", post: post)
  end

  def random(conn, _) do
    query = from post in Post,
      order_by: fragment("random()"),
      limit: 1,
      preload: [:channel, :developer]

    post = Repo.one(query)

    render(conn, "show.html", post: post)
  end

  def new(conn, _params) do
    changeset = Post.changeset(%Post{})
    render conn, "new.html", changeset: changeset
  end

  def like(conn, %{"slug" => slug}) do
    likes = Tilex.Liking.like(slug)

    conn
    |> put_resp_content_type("application/json")
    |> send_resp(200, Poison.encode!(%{likes: likes}))
  end

  def unlike(conn, %{"slug" => slug}) do
    likes = Tilex.Liking.unlike(slug)

    conn
    |> put_resp_content_type("application/json")
    |> send_resp(200, Poison.encode!(%{likes: likes}))
  end

  def create(conn, %{"post" => post_params}) do
    developer = Guardian.Plug.current_resource(conn)

    changeset =
      %Post{}
      |> Map.put(:developer_id, developer.id)
      |> Post.changeset(post_params)

    case Repo.insert(changeset) do
      {:ok, post} ->
        conn
        |> put_flash(:info, "Post created")
        |> redirect(to: post_path(conn, :index))
        |> Tilex.Integrations.notify(post)

      {:error, changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end

  def edit(conn, _params) do
    current_user = Guardian.Plug.current_resource(conn)

    post = case current_user.admin do
      false -> current_user
           |> assoc(:posts)
           |> Repo.get_by!(slug: conn.assigns.slug)
      true -> Repo.get_by!(Post, slug: conn.assigns.slug)
    end

    changeset = Post.changeset(post)

    render(conn, "edit.html", post: post, changeset: changeset)
  end

  def update(conn, %{"post" => post_params}) do
    current_user = Guardian.Plug.current_resource(conn)

    post = case current_user.admin do
      false -> current_user
           |> assoc(:posts)
           |> Repo.get_by!(slug: conn.assigns.slug)
      true -> Repo.get_by!(Post, slug: conn.assigns.slug)
    end

    changeset = Post.changeset(post, post_params)

    case Repo.update(changeset) do
      {:ok, post} ->
        conn
        |> put_flash(:info, "Post Updated")
        |> redirect(to: post_path(conn, :show, post))
      {:error, changeset} ->
        render(conn, "edit.html", post: post, changeset: changeset)
    end
  end

  defp load_channels(conn, _) do
    query = Channel
    |> Channel.names_and_ids
    |> Channel.alphabetized

    channels = Repo.all(query)
    assign(conn, :channels, channels)
  end

  defp extract_slug(conn, _) do
    case extracted_slug(conn.params["titled_slug"]) do
      {:ok, slug} ->
        assign(conn, :slug, slug)
      :error ->
        conn
        |> put_status(404)
        |> render(TilexWeb.ErrorView, "404.html")
        |> halt()
    end
  end

  defp extracted_slug(<<slug::size(10)-binary, _rest::binary>>), do: {:ok, slug}
  defp extracted_slug(_), do: :error
end