update the users

update the users

defmodule Flour.PostController do
  use Flour.Web, :controller
  require IEx
  require Exredis
  plug :put_layout, "post.html"
  alias Flour.Post
  alias Flour.Photo
  alias Flour.User

  def index(conn, _params) do
    posts = Repo.all(Post)
    render(conn, "index.html", posts: posts)
  end

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

  def create(conn, %{"post" => post_params, "photos" => photo_ids}) do
    openid = get_session(conn, :openid)
    users =  Repo.get_by(User, openid: openid)
    changeset = Post.changeset(%Post{openid: openid, user_id: hd(users)[:id]}, post_params)
    #changeset = Post.changeset(%Post{}, post_params)
    ps = String.split(photo_ids, ",")
      |> Enum.map( &(String.to_integer(&1)) )
    # query = from p in Photo, where: p.id in ^ps
    # photos = Repo.all(query)

    case Repo.insert(changeset) do
      {:ok, _post} ->
        from( p in Photo, where: p.id in ^ps)
         |> Repo.update_all(set: [post_id: _post.id])
        conn
         |> put_flash(:info, "Post created successfully.")
         |> redirect(to: post_path(conn, :index))
      {:error, changeset} ->
        # json conn, changeset
        render(conn, "new.html", changeset: changeset)
    end
  end

  def show(conn, %{"id" => id, "code"=> code, "state"=> state}) do
    appid = "#{Application.get_env(:flour, :wechat_appid)}"
    secret = "#{Application.get_env(:flour, :wechat_secret)}"
    userinfo_base = "#{Application.get_env(:flour, :wechat_userinfo_url)}"
    access_url = "#{Application.get_env(:flour, :wechat_access_token_url)}?appid=#{appid}&secret=#{secret}&code=#{code}&grant_type=authorization_code"
    IO.puts access_url

    #{:ok,client} = Exredis.start_link
    #foo = (client |> Exredis.query ["GET","FOO"] )
    #IO.puts foo
    #if foo == :undefined do
    #  IO.puts foo
    #end
    #client |> Exredis.stop

    conn = put_session(conn, :message, "oksdfad")
    message = get_session(conn, :message)
    IO.puts "message: #{message}"
    access_token = get_session(conn, :access_token)
    IO.puts "access_token"
    if !access_token do
      IO.puts "access_token is empty"
			case HTTPoison.get(access_url) do
				{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
          result = Poison.Parser.parse!(body)
          IO.inspect result
          conn = put_session(conn, :access_token, result["access_token"])
          conn = put_session(conn, :openid, result["openid"])
				{:ok, %HTTPoison.Response{status_code: 404}} ->
					IO.puts "Not found :("
				{:error, %HTTPoison.Error{reason: reason}} ->
					IO.inspect reason
			end

    end
    access_token = get_session(conn, :access_token)
    openid = get_session(conn, :openid)
    userinfo_url = "#{userinfo_base}?openid=#{openid}&access_token=#{access_token}"
    IO.puts "access_token: #{access_token}"
    IO.puts "openid: #{openid}"
    user =  Repo.get_by(User, openid: openid)

    # IO.puts "user: #{Length(user)}"
    case HTTPoison.get(userinfo_url) do
      {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
        result = Poison.Parser.parse!(body)
        IO.inspect result
        changeset = User.changeset(%User{}, %{"openid"=> result["openid"], "nickname"=> result["nickname"],
            "province"=> result["province"],
             "country"=> result["country"],
             "headimgurl"=> result["headimgurl"]})

        if !users do

          IO.puts "save"
          Repo.insert(changeset)
        else
          IO.puts "update"
          Repo.update(user,changeset)
        end
      {:ok, %HTTPoison.Response{status_code: 404}} ->
        IO.puts "Not found :("
      {:error, %HTTPoison.Error{reason: reason}} ->
        IO.inspect reason
    end

    post = Repo.get!(Post, id) |> Repo.preload(:photos)
    render(conn, "show.html", post: post, layout: {Flour.LayoutView, "app.html"})
  end

  def edit(conn, %{"id" => id}) do
    post = Repo.get!(Post, id)
    changeset = Post.changeset(post)
    render(conn, "edit.html", post: post, changeset: changeset)
  end

  def update(conn, %{"id" => id, "post" => post_params}) do
    IO.inspect post_params
    post = Repo.get!(Post, id)
    changeset = Post.changeset(post, post_params)

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

  def delete(conn, %{"id" => id}) do
    post = Repo.get!(Post, id)

    # Here we use delete! (with a bang) because we expect
    # it to always work (and if it does not, it will raise).
    Repo.delete!(post)

    conn
    |> put_flash(:info, "Post deleted successfully.")
    |> redirect(to: post_path(conn, :index))
  end
end

defmodule Flour.PostController do
  use Flour.Web, :controller
  require IEx
  require Exredis
  plug :put_layout, "post.html"
  alias Flour.Post
  alias Flour.Photo
  alias Flour.User

  def index(conn, _params) do
    posts = Repo.all(Post)
    render(conn, "index.html", posts: posts)
  end

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

  def create(conn, %{"post" => post_params, "photos" => photo_ids}) do
    openid = get_session(conn, :openid)
    users =  Repo.get_by(User, openid: openid)
    changeset = Post.changeset(%Post{openid: openid, user_id: hd(users)[:id]}, post_params)
    #changeset = Post.changeset(%Post{}, post_params)
    ps = String.split(photo_ids, ",")
      |> Enum.map( &(String.to_integer(&1)) )
    # query = from p in Photo, where: p.id in ^ps
    # photos = Repo.all(query)

    case Repo.insert(changeset) do
      {:ok, _post} ->
        from( p in Photo, where: p.id in ^ps)
         |> Repo.update_all(set: [post_id: _post.id])
        conn
         |> put_flash(:info, "Post created successfully.")
         |> redirect(to: post_path(conn, :index))
      {:error, changeset} ->
        # json conn, changeset
        render(conn, "new.html", changeset: changeset)
    end
  end

  def show(conn, %{"id" => id, "code"=> code, "state"=> state}) do
    appid = "#{Application.get_env(:flour, :wechat_appid)}"
    secret = "#{Application.get_env(:flour, :wechat_secret)}"
    userinfo_base = "#{Application.get_env(:flour, :wechat_userinfo_url)}"
    access_url = "#{Application.get_env(:flour, :wechat_access_token_url)}?appid=#{appid}&secret=#{secret}&code=#{code}&grant_type=authorization_code"
    IO.puts access_url

    #{:ok,client} = Exredis.start_link
    #foo = (client |> Exredis.query ["GET","FOO"] )
    #IO.puts foo
    #if foo == :undefined do
    #  IO.puts foo
    #end
    #client |> Exredis.stop

    conn = put_session(conn, :message, "oksdfad")
    message = get_session(conn, :message)
    IO.puts "message: #{message}"
    access_token = get_session(conn, :access_token)
    IO.puts "access_token"
    if !access_token do
      IO.puts "access_token is empty"
			case HTTPoison.get(access_url) do
				{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
          result = Poison.Parser.parse!(body)
          IO.inspect result
          conn = put_session(conn, :access_token, result["access_token"])
          conn = put_session(conn, :openid, result["openid"])
				{:ok, %HTTPoison.Response{status_code: 404}} ->
					IO.puts "Not found :("
				{:error, %HTTPoison.Error{reason: reason}} ->
					IO.inspect reason
			end

    end
    access_token = get_session(conn, :access_token)
    openid = get_session(conn, :openid)
    userinfo_url = "#{userinfo_base}?openid=#{openid}&access_token=#{access_token}"
    IO.puts "access_token: #{access_token}"
    IO.puts "openid: #{openid}"
    users =  Repo.get_by(User, openid: openid)

    # IO.puts "user: #{Length(user)}"
    case HTTPoison.get(userinfo_url) do
      {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
        result = Poison.Parser.parse!(body)
        IO.inspect result
        changeset = User.changeset(%User{}, %{"openid"=> result["openid"], "nickname"=> result["nickname"],
            "province"=> result["province"],
             "country"=> result["country"],
             "headimgurl"=> result["headimgurl"]})

        if !users do

          IO.puts "save"
          Repo.insert(changeset)
        else
          IO.puts "update"
          Repo.update(hd(users),changeset)
        end
      {:ok, %HTTPoison.Response{status_code: 404}} ->
        IO.puts "Not found :("
      {:error, %HTTPoison.Error{reason: reason}} ->
        IO.inspect reason
    end

    post = Repo.get!(Post, id) |> Repo.preload(:photos)
    render(conn, "show.html", post: post, layout: {Flour.LayoutView, "app.html"})
  end

  def edit(conn, %{"id" => id}) do
    post = Repo.get!(Post, id)
    changeset = Post.changeset(post)
    render(conn, "edit.html", post: post, changeset: changeset)
  end

  def update(conn, %{"id" => id, "post" => post_params}) do
    IO.inspect post_params
    post = Repo.get!(Post, id)
    changeset = Post.changeset(post, post_params)

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

  def delete(conn, %{"id" => id}) do
    post = Repo.get!(Post, id)

    # Here we use delete! (with a bang) because we expect
    # it to always work (and if it does not, it will raise).
    Repo.delete!(post)

    conn
    |> put_flash(:info, "Post deleted successfully.")
    |> redirect(to: post_path(conn, :index))
  end
end


feat(user): update controller

feat(user): update controller

defmodule Lyn.UserController do
  use Lyn.Web, :controller

  alias Lyn.Repo
  alias Lyn.User
  alias Lyn.Authorization

  def new(conn, params, current_user, _claims) do
    render conn, "new.html", current_user: current_user
  end
end

defmodule Lyn.UserController do
  use Lyn.Web, :controller

  alias Lyn.User
  alias Lyn.Role

  plug :scrub_params, "user" when action in [:create, :update]
  plug :authorize_admin when action in [:new, :create]
  plug :authorize_user when action in [:edit, :update, :delete]

  def index(conn, _params) do
    users = Repo.all(User)
    render(conn, "index.html", users: users)
  end

  def new(conn, _params) do
    roles = Repo.all(Role)
    changeset = User.changeset(%User{})
    render(conn, "new.html", changeset: changeset, roles: roles)
  end

  def create(conn, %{"user" => user_params}) do
    roles = Repo.all(Role)
    changeset = User.changeset(%User{}, user_params)

    case Repo.insert(changeset) do
      {:ok, _user} ->
        conn
        |> put_flash(:info, "User created successfully.")
        |> redirect(to: user_path(conn, :index))
      {:error, changeset} ->
        render(conn, "new.html", changeset: changeset, roles: roles)
    end
  end

  def show(conn, %{"id" => id}) do
    user = Repo.get!(User, id)
    render(conn, "show.html", user: user)
  end

  def edit(conn, %{"id" => id}) do
    roles = Repo.all(Role)
    user = Repo.get!(User, id)
    changeset = User.changeset(user)
    render(conn, "edit.html", user: user, changeset: changeset, roles: roles)
  end

  def update(conn, %{"id" => id, "user" => user_params}) do
    roles = Repo.all(Role)
    user = Repo.get!(User, id)
    changeset = User.changeset(user, user_params)

    case Repo.update(changeset) do
      {:ok, user} ->
        conn
        |> put_flash(:info, "User updated successfully.")
        |> redirect(to: user_path(conn, :show, user))
      {:error, changeset} ->
        render(conn, "edit.html", user: user, changeset: changeset, roles: roles)
    end
  end

  def delete(conn, %{"id" => id}) do
    user = Repo.get!(User, id)

    # Here we use delete! (with a bang) because we expect
    # it to always work (and if it does not, it will raise).
    Repo.delete!(user)

    conn
    |> put_flash(:info, "User deleted successfully.")
    |> redirect(to: user_path(conn, :index))
  end

  defp authorize_user(conn, _) do
    user = get_session(conn, :current_user)
    if user && (Integer.to_string(user.id) == conn.params["id"] || Lyn.RoleChecker.is_admin?(user)) do
      conn
    else
      conn
      |> put_flash(:error, "You are not authorized to modify that user!")
      |> redirect(to: page_path(conn, :index))
      |> halt()
    end
  end

  defp authorize_admin(conn, _) do
    user = get_session(conn, :current_user)
    if user && Lyn.RoleChecker.is_admin?(user) do
      conn
    else
      conn
      |> put_flash(:error, "You are not authorized to create new users!")
      |> redirect(to: page_path(conn, :index))
      |> halt()
    end
  end
end


Set update_available flag when after a message from NervesHub

defmodule FarmbotOS.Platform.Target.NervesHubClient do
  @moduledoc """
  NervesHub.Client implementation.

  This should be one of the very first processes to be started.
  Because it is started so early, it has to check for things that
  might not be available in the environment. Environment is checked
  in this order:
  * token
  * email
  * amqp access (relies on network + NTP)
  * `DeviceCert`
  * nerves_hub `cert` + `key`

  ## State flow

  The basic flow of state changes follows that path as well.
  1) check for nerves_hub `cert` and `key`
    1a) if `cert` and `key` are available goto 4.
    1b) if not, goto 2.
  2) check for Farmbot `DeviceCert`.
    2a) if available update tags. goto 3.
    2b) if not avialable, create. goto 3.
  3) Wait for Farmbot API to dispatch nerves_hub `cert` and `key`.
  4) When `cert` and `key` is available, try to connect to `nerves_hub`.

  ## After connection

  While connected to NervesHub this process implements the
  `NervesHub.Client` behaviour. When an update becomes available from a
  NervesHub deployment, `update_available` will be called. This should
  check if the Farmbot settings allow for auto updating. If so, apply the
  update, if not wait for a CeleryScript request to update via `check_update`
  """

  use GenServer
  use AMQP

  alias AMQP.{
    Channel,
    Queue
  }

  alias FarmbotCore.Project
  alias FarmbotCore.{BotState, BotState.JobProgress.Percent, Config}
  alias FarmbotCore.{Asset, Config, JSON}
  alias FarmbotExt.JWT
  require FarmbotCore.Logger
  require Logger

  alias FarmbotExt.AMQP.ConnectionWorker
  @behaviour NervesHub.Client

  @exchange "amq.topic"

  defstruct [
    :conn,
    :chan,
    :jwt,
    :key,
    :cert,
    :is_applying_update,
    :firmware_url,
    :probably_connected
  ]

  alias __MODULE__, as: State

  @doc false
  def start_link(args) do
    GenServer.start_link(__MODULE__, args, name: __MODULE__)
  end

  @doc "Returns the serial number of this device"
  def serial_number(:rpi0), do: serial_number("rpi")
  def serial_number(:rpi3), do: serial_number("rpi")

  def serial_number(plat) do
    :os.cmd(
      '/usr/bin/boardid -b uboot_env -u nerves_serial_number -b uboot_env -u serial_number -b #{
        plat
      }'
    )
    |> to_string()
    |> String.trim()
  end

  @doc "Returns the serial number of this device"
  def serial_number, do: serial_number(Project.target())

  @doc "Returns the uuid of the running firmware"
  def uuid, do: Nerves.Runtime.KV.get_active("nerves_fw_uuid")

  @doc "Loads the cert from storage"
  def load_cert, do: Nerves.Runtime.KV.get_active("nerves_hub_cert") |> filter_parens()

  @doc "Loads the key from storage"
  def load_key, do: Nerves.Runtime.KV.get_active("nerves_hub_key") |> filter_parens()

  @doc false
  def write_serial(serial_number) do
    Nerves.Runtime.KV.UBootEnv.put("nerves_serial_number", serial_number)
    Nerves.Runtime.KV.UBootEnv.put("nerves_fw_serial_number", serial_number)
  end

  @doc false
  def write_cert(cert) do
    Nerves.Runtime.KV.UBootEnv.put("nerves_hub_cert", cert)
  end

  @doc false
  def write_key(key) do
    Nerves.Runtime.KV.UBootEnv.put("nerves_hub_key", key)
  end

  @impl NervesHub.Client
  def handle_error(error) do
    GenServer.cast(__MODULE__, {:handle_nerves_hub_error, error})
    :ok
  end

  @impl NervesHub.Client
  def handle_fwup_message(msg) do
    GenServer.cast(__MODULE__, {:handle_nerves_hub_fwup_message, msg})
    :ok
  end

  @impl NervesHub.Client
  def update_available(data) do
    GenServer.call(__MODULE__, {:handle_nerves_hub_update_available, data})
  end

  @doc """
  Checks if an update is available, and applies it.
  """
  def check_update do
    GenServer.call(__MODULE__, :check_update)
  end

  def do_restart_nerves_hub do
    try do
      # NervesHub replaces it's own env on startup. Reset it.
      # Stop Nerves Hub if it is running.
      _ = Supervisor.terminate_child(FarmbotOS.Init.Supervisor, NervesHub.Supervisor)
      _ = Supervisor.delete_child(FarmbotOS.Init.Supervisor, NervesHub.Supervisor)

      cert = load_cert()
      key = load_key()

      if cert && key do
      end

      # Cause NervesRuntime.KV to restart.
      # _ = GenServer.stop(Nerves.Runtime.KV, :restart)
      _ = Application.stop(:nerves_runtime)
      Process.sleep(1000)
      _ = Application.ensure_all_started(:nerves_runtime)
      _ = Application.ensure_all_started(:nerves_hub)

      # Wait for a few seconds for good luck.
      Process.sleep(1000)
    catch
      kind, err ->
        IO.warn("NervesHub error: #{inspect(kind)} #{inspect(err)}", __STACKTRACE__)
        FarmbotCore.Logger.error(1, "OTA service error: #{kind} #{inspect(err)}")
    end

    # Start the connection again.
    Supervisor.start_child(FarmbotOS, NervesHub.Supervisor)
  end

  @impl GenServer
  def init(_args) do
    Application.ensure_all_started(:nerves_runtime)
    Application.ensure_all_started(:nerves_hub)
    write_serial(serial_number())
    Process.flag(:sensitive, true)
    cert = load_cert()
    key = load_key()

    if cert && key do
      send(self(), :connect_nerves_hub)
    else
      send(self(), :connect_amqp)
    end

    {:ok,
     %State{
       conn: nil,
       chan: nil,
       jwt: nil,
       cert: cert,
       key: key,
       is_applying_update: false,
       probably_connected: false
     }}
  end

  @impl GenServer
  def terminate(reason, state) do
    FarmbotCore.Logger.error(1, "Disconnected from NervesHub AMQP channel: #{inspect(reason)}")
    # If a channel was still open, close it.
    if state.chan, do: AMQP.Channel.close(state.chan)
  end

  @impl GenServer
  def handle_info(:connect_amqp, %{conn: nil, chan: nil} = state) do
    FarmbotCore.Logger.debug(1, "Attempting to get OTA certs from AMQP.")

    with {token, %{} = jwt} when is_binary(token) <- get_jwt(),
         email when is_binary(email) <- get_email(),
         {:ok, %{} = conn} <- open_connection(email, token, jwt),
         {:ok, chan} <- Channel.open(conn),
         :ok <- Basic.qos(chan, global: true),
         {:ok, _} <-
           Queue.declare(chan, "#{jwt.bot}_nerves_hub", auto_delete: false, durable: true),
         :ok <-
           Queue.bind(chan, "#{jwt.bot}_nerves_hub", @exchange,
             routing_key: "bot.#{jwt.bot}.nerves_hub"
           ),
         {:ok, _tag} <- Basic.consume(chan, "#{jwt.bot}_nerves_hub", self(), []) do
      send(self(), :after_connect_amqp)
      {:noreply, %{state | conn: conn, chan: chan, jwt: jwt}}
    else
      # happens when no token is configured.
      {nil, nil} ->
        FarmbotCore.Logger.debug(3, "No credentials yet. Can't connect to OTA Server.")
        Process.send_after(self(), :connect_amqp, 15_000)
        {:noreply, %{state | conn: nil, chan: nil, jwt: nil}}

      err ->
        FarmbotCore.Logger.error(
          3,
          "Failed to connect to NervesHub AMQP channel: #{inspect(err)}"
        )

        Process.send_after(self(), :connect_amqp, 5000)
        {:noreply, %{state | conn: nil, chan: nil, jwt: nil}}
    end
  end

  def handle_info(:after_connect_amqp, %{key: nil, cert: nil} = state) do
    FarmbotCore.Logger.debug(3, "Connected to NervesHub AMQP channel. Fetching certs.")
    old_device_cert = Asset.get_device_cert(serial_number: serial_number())

    tags = detect_deployment_tags()

    params = %{
      serial_number: serial_number(),
      tags: tags
    }

    new_device_cert =
      case old_device_cert do
        nil -> Asset.new_device_cert(params)
        %{} -> Asset.update_device_cert(old_device_cert, params)
      end

    case new_device_cert do
      {:ok, data} ->
        # DO NOT DO THIS. The api will do it behind the scenes
        # Asset.update_device!(%{update_channel: detect_update_channel()})
        FarmbotCore.Logger.debug(3, "DeviceCert created: #{inspect(data)}")
        FarmbotCore.Logger.debug(3, "Waiting for cert and key data from AMQP from farmbot api...")
        {:noreply, state}

      {:error, reason} ->
        FarmbotCore.Logger.error(1, "Failed to create device cert: #{inspect(reason)}")
        Process.send_after(self(), :after_connect_amqp, 5000)
        {:noreply, state}
    end
  end

  def handle_info(:after_connect_amqp, %{key: _key, cert: _cert} = state) do
    FarmbotCore.Logger.debug(3, "Connected to NervesHub AMQP channel. Certs already loaded")
    send(self(), :connect_nerves_hub)
    {:noreply, state}
  end

  def handle_info(:connect_nerves_hub, %{key: nil, cert: nil} = state) do
    FarmbotCore.Logger.debug(3, "Can't connect to OTA Service. Certs not loaded")
    send(self(), :connect_amqp)
    {:noreply, state}
  end

  def handle_info(
        :connect_nerves_hub,
        %{key: _key, cert: _cert, probably_connected: false} = state
      ) do
    FarmbotCore.Logger.debug(3, "Starting OTA Service")
    do_restart_nerves_hub()
    FarmbotCore.Logger.debug(3, "OTA Service started")
    {:noreply, %{state | probably_connected: true}}
  end

  def handle_info(
        :connect_nerves_hub,
        %{key: _key, cert: _cert, probably_connected: true} = state
      ) do
    {:noreply, state}
  end

  # Confirmation sent by the broker after registering this process as a consumer
  def handle_info({:basic_consume_ok, _}, state) do
    {:noreply, state}
  end

  # Sent by the broker when the consumer is
  # unexpectedly cancelled (such as after a queue deletion)
  def handle_info({:basic_cancel, _}, state) do
    {:stop, :normal, state}
  end

  # Confirmation sent by the broker to the consumer process after a Basic.cancel
  def handle_info({:basic_cancel_ok, _}, state) do
    {:noreply, state}
  end

  def handle_info({:basic_deliver, payload, %{routing_key: key}}, state) do
    device = state.jwt.bot
    ["bot", ^device, "nerves_hub"] = String.split(key, ".")

    with {:ok, %{"cert" => base64_cert, "key" => base64_key}} <- JSON.decode(payload),
         {:ok, cert} <- Base.decode64(base64_cert),
         {:ok, key} <- Base.decode64(base64_key),
         :ok <- write_cert(cert),
         :ok <- write_key(key) do
      send(self(), :connect_nerves_hub)
      {:noreply, %{state | cert: cert, key: key}}
    else
      {:error, reason} ->
        FarmbotCore.Logger.error(1, "OTA Service failed to configure. #{inspect(reason)}")
        {:stop, reason, state}

      :error ->
        FarmbotCore.Logger.error(1, "OTA Service payload invalid. (base64)")
        {:stop, :invalid_payload, state}
    end
  end

  @impl GenServer
  def handle_cast({:handle_nerves_hub_error, error}, %{is_applying_update: true} = state) do
    FarmbotCore.Logger.error(1, "Error applying OTA: #{inspect(error)}")
    {:noreply, state}
  end

  def handle_cast({:handle_nerves_hub_error, error}, state) do
    FarmbotCore.Logger.debug(3, "Unexpected NervesHub error: #{inspect(error)}")
    {:noreply, state}
  end

  def handle_cast({:handle_nerves_hub_fwup_message, {:progress, percent}}, state) do
    _ = set_ota_progress(percent)
    {:noreply, state}
  end

  def handle_cast({:handle_nerves_hub_fwup_message, {:ok, _, _info}}, state) do
    _ = set_firmware_needs_flash()
    _ = set_ota_progress(100)
    {:noreply, state}
  end

  def handle_cast({:handle_nerves_hub_fwup_message, {:error, _, reason}}, state) do
    _ = set_ota_progress(100)
    FarmbotCore.Logger.error(1, "Error applying OTA: #{inspect(reason)}")
    {:noreply, state}
  end

  def handle_cast({:handle_nerves_hub_fwup_message, _}, state) do
    {:noreply, state}
  end

  @impl GenServer
  def handle_call({:handle_nerves_hub_update_available, %{"firmware_url" => url}}, _from, state) do
    case Asset.fbos_config(:os_auto_update) do
      true ->
        set_update_available_in_bot_state()
        FarmbotCore.Logger.busy(1, "Applying OTA update")
        _ = set_firmware_needs_flash()
        {:reply, :apply, %{state | is_applying_update: true, firmware_url: url}}

      _ ->
        set_update_available_in_bot_state()
        FarmbotCore.Logger.info(1, "New Farmbot OS is available!")
        {:reply, :ignore, %{state | firmware_url: url}}
    end
  end

  def handle_call({:handle_nerves_hub_update_available, _data}, _from, state) do
    set_update_available_in_bot_state()
    FarmbotCore.Logger.busy(1, "Applying OTA update")
    {:reply, :apply, %{state | is_applying_update: true}}
  end

  def handle_call(:check_update, _from, state) do
    case NervesHub.HTTPClient.update() do
      {:ok, %{"data" => %{"update_available" => false}}} ->
        {:reply, nil, state}

      data ->
        set_update_available_in_bot_state()
        FarmbotCore.Logger.busy(1, "Applying OTA update")
        _ = set_firmware_needs_flash()

        spawn_link(fn -> NervesHub.update() end)
        {:reply, data, %{state | is_applying_update: true}}
    end
  end

  defp set_update_available_in_bot_state() do
    if Process.whereis(BotState) do
      BotState.set_update_available(true)
    end
  end

  defp get_jwt do
    token = Config.get_config_value(:string, "authorization", "token")

    if token do
      {:ok, jwt} = JWT.decode(token)
      {token, jwt}
    else
      {nil, nil}
    end
  end

  defp get_email do
    Config.get_config_value(:string, "authorization", "email")
  end

  defp open_connection(email, token, jwt) do
    case ConnectionWorker.open_connection(token, email, jwt.bot, jwt.mqtt, jwt.vhost) do
      {:ok, conn} ->
        Process.link(conn.pid)
        Process.monitor(conn.pid)
        {:ok, conn}

      err ->
        FarmbotCore.Logger.error(1, "Error connecting to AMQP: #{inspect(err)}")
        err
    end
  end

  defp filter_parens(""), do: nil
  defp filter_parens(data), do: data

  defp set_ota_progress(100) do
    FarmbotCore.Logger.success(1, "OTA Complete Going down for reboot")
    prog = %Percent{percent: 100, status: "complete"}

    if Process.whereis(BotState) do
      BotState.set_job_progress("FBOS_OTA", prog)
    end

    :ok
  end

  defp set_ota_progress(percent) do
    prog = %Percent{percent: percent}

    if Process.whereis(BotState) do
      BotState.set_job_progress("FBOS_OTA", prog)
    end

    :ok
  end

  def set_firmware_needs_flash() do
    Config.update_config_value(:bool, "settings", "firmware_needs_flash", true)
    Config.update_config_value(:bool, "settings", "firmware_needs_open", false)
    :ok
  end

  def detect_deployment_tags() do
    update_channel = detect_update_channel()
    ["application:#{Project.env()}", "channel:#{update_channel}"]
  end

  def detect_update_channel() do
    if Regex.match?(
         ~r/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)-rc(0|[1-9]\d*)+?/,
         Project.version()
       ) do
      "beta"
    else
      case Project.branch() do
        "master" -> "stable"
        branch -> branch
      end
    end
  end
end

defmodule FarmbotOS.Platform.Target.NervesHubClient do
  @moduledoc """
  NervesHub.Client implementation.

  This should be one of the very first processes to be started.
  Because it is started so early, it has to check for things that
  might not be available in the environment. Environment is checked
  in this order:
  * token
  * email
  * amqp access (relies on network + NTP)
  * `DeviceCert`
  * nerves_hub `cert` + `key`

  ## State flow

  The basic flow of state changes follows that path as well.
  1) check for nerves_hub `cert` and `key`
    1a) if `cert` and `key` are available goto 4.
    1b) if not, goto 2.
  2) check for Farmbot `DeviceCert`.
    2a) if available update tags. goto 3.
    2b) if not avialable, create. goto 3.
  3) Wait for Farmbot API to dispatch nerves_hub `cert` and `key`.
  4) When `cert` and `key` is available, try to connect to `nerves_hub`.

  ## After connection

  While connected to NervesHub this process implements the
  `NervesHub.Client` behaviour. When an update becomes available from a
  NervesHub deployment, `update_available` will be called. This should
  check if the Farmbot settings allow for auto updating. If so, apply the
  update, if not wait for a CeleryScript request to update via `check_update`
  """

  use GenServer
  use AMQP

  alias AMQP.{
    Channel,
    Queue
  }

  alias FarmbotCore.Project
  alias FarmbotCore.{BotState, BotState.JobProgress.Percent, Config}
  alias FarmbotCore.{Asset, Config, JSON}
  alias FarmbotExt.JWT
  require FarmbotCore.Logger
  require Logger

  alias FarmbotExt.AMQP.ConnectionWorker
  @behaviour NervesHub.Client

  @exchange "amq.topic"

  defstruct [
    :conn,
    :chan,
    :jwt,
    :key,
    :cert,
    :is_applying_update,
    :firmware_url,
    :probably_connected
  ]

  alias __MODULE__, as: State

  @doc false
  def start_link(args) do
    GenServer.start_link(__MODULE__, args, name: __MODULE__)
  end

  @doc "Returns the serial number of this device"
  def serial_number(:rpi0), do: serial_number("rpi")
  def serial_number(:rpi3), do: serial_number("rpi")

  def serial_number(plat) do
    :os.cmd(
      '/usr/bin/boardid -b uboot_env -u nerves_serial_number -b uboot_env -u serial_number -b #{
        plat
      }'
    )
    |> to_string()
    |> String.trim()
  end

  @doc "Returns the serial number of this device"
  def serial_number, do: serial_number(Project.target())

  @doc "Returns the uuid of the running firmware"
  def uuid, do: Nerves.Runtime.KV.get_active("nerves_fw_uuid")

  @doc "Loads the cert from storage"
  def load_cert, do: Nerves.Runtime.KV.get_active("nerves_hub_cert") |> filter_parens()

  @doc "Loads the key from storage"
  def load_key, do: Nerves.Runtime.KV.get_active("nerves_hub_key") |> filter_parens()

  @doc false
  def write_serial(serial_number) do
    Nerves.Runtime.KV.UBootEnv.put("nerves_serial_number", serial_number)
    Nerves.Runtime.KV.UBootEnv.put("nerves_fw_serial_number", serial_number)
  end

  @doc false
  def write_cert(cert) do
    Nerves.Runtime.KV.UBootEnv.put("nerves_hub_cert", cert)
  end

  @doc false
  def write_key(key) do
    Nerves.Runtime.KV.UBootEnv.put("nerves_hub_key", key)
  end

  @impl NervesHub.Client
  def handle_error(error) do
    GenServer.cast(__MODULE__, {:handle_nerves_hub_error, error})
    :ok
  end

  @impl NervesHub.Client
  def handle_fwup_message(msg) do
    GenServer.cast(__MODULE__, {:handle_nerves_hub_fwup_message, msg})
    :ok
  end

  @impl NervesHub.Client
  def update_available(data) do
    GenServer.call(__MODULE__, {:handle_nerves_hub_update_available, data})
  end

  @doc """
  Checks if an update is available, and applies it.
  """
  def check_update do
    GenServer.call(__MODULE__, :check_update)
  end

  def do_restart_nerves_hub do
    try do
      # NervesHub replaces it's own env on startup. Reset it.
      # Stop Nerves Hub if it is running.
      _ = Supervisor.terminate_child(FarmbotOS.Init.Supervisor, NervesHub.Supervisor)
      _ = Supervisor.delete_child(FarmbotOS.Init.Supervisor, NervesHub.Supervisor)

      cert = load_cert()
      key = load_key()

      if cert && key do
      end

      # Cause NervesRuntime.KV to restart.
      # _ = GenServer.stop(Nerves.Runtime.KV, :restart)
      _ = Application.stop(:nerves_runtime)
      Process.sleep(1000)
      _ = Application.ensure_all_started(:nerves_runtime)
      _ = Application.ensure_all_started(:nerves_hub)

      # Wait for a few seconds for good luck.
      Process.sleep(1000)
    catch
      kind, err ->
        IO.warn("NervesHub error: #{inspect(kind)} #{inspect(err)}", __STACKTRACE__)
        FarmbotCore.Logger.error(1, "OTA service error: #{kind} #{inspect(err)}")
    end

    # Start the connection again.
    Supervisor.start_child(FarmbotOS, NervesHub.Supervisor)
  end

  @impl GenServer
  def init(_args) do
    Application.ensure_all_started(:nerves_runtime)
    Application.ensure_all_started(:nerves_hub)
    write_serial(serial_number())
    Process.flag(:sensitive, true)
    cert = load_cert()
    key = load_key()

    if cert && key do
      send(self(), :connect_nerves_hub)
    else
      send(self(), :connect_amqp)
    end

    {:ok,
     %State{
       conn: nil,
       chan: nil,
       jwt: nil,
       cert: cert,
       key: key,
       is_applying_update: false,
       probably_connected: false
     }}
  end

  @impl GenServer
  def terminate(reason, state) do
    FarmbotCore.Logger.error(1, "Disconnected from NervesHub AMQP channel: #{inspect(reason)}")
    # If a channel was still open, close it.
    if state.chan, do: AMQP.Channel.close(state.chan)
  end

  @impl GenServer
  def handle_info(:connect_amqp, %{conn: nil, chan: nil} = state) do
    FarmbotCore.Logger.debug(1, "Attempting to get OTA certs from AMQP.")

    with {token, %{} = jwt} when is_binary(token) <- get_jwt(),
         email when is_binary(email) <- get_email(),
         {:ok, %{} = conn} <- open_connection(email, token, jwt),
         {:ok, chan} <- Channel.open(conn),
         :ok <- Basic.qos(chan, global: true),
         {:ok, _} <-
           Queue.declare(chan, "#{jwt.bot}_nerves_hub", auto_delete: false, durable: true),
         :ok <-
           Queue.bind(chan, "#{jwt.bot}_nerves_hub", @exchange,
             routing_key: "bot.#{jwt.bot}.nerves_hub"
           ),
         {:ok, _tag} <- Basic.consume(chan, "#{jwt.bot}_nerves_hub", self(), []) do
      send(self(), :after_connect_amqp)
      {:noreply, %{state | conn: conn, chan: chan, jwt: jwt}}
    else
      # happens when no token is configured.
      {nil, nil} ->
        FarmbotCore.Logger.debug(3, "No credentials yet. Can't connect to OTA Server.")
        Process.send_after(self(), :connect_amqp, 15_000)
        {:noreply, %{state | conn: nil, chan: nil, jwt: nil}}

      err ->
        FarmbotCore.Logger.error(
          3,
          "Failed to connect to NervesHub AMQP channel: #{inspect(err)}"
        )

        Process.send_after(self(), :connect_amqp, 5000)
        {:noreply, %{state | conn: nil, chan: nil, jwt: nil}}
    end
  end

  def handle_info(:after_connect_amqp, %{key: nil, cert: nil} = state) do
    FarmbotCore.Logger.debug(3, "Connected to NervesHub AMQP channel. Fetching certs.")
    old_device_cert = Asset.get_device_cert(serial_number: serial_number())

    tags = detect_deployment_tags()

    params = %{
      serial_number: serial_number(),
      tags: tags
    }

    new_device_cert =
      case old_device_cert do
        nil -> Asset.new_device_cert(params)
        %{} -> Asset.update_device_cert(old_device_cert, params)
      end

    case new_device_cert do
      {:ok, data} ->
        # DO NOT DO THIS. The api will do it behind the scenes
        # Asset.update_device!(%{update_channel: detect_update_channel()})
        FarmbotCore.Logger.debug(3, "DeviceCert created: #{inspect(data)}")
        FarmbotCore.Logger.debug(3, "Waiting for cert and key data from AMQP from farmbot api...")
        {:noreply, state}

      {:error, reason} ->
        FarmbotCore.Logger.error(1, "Failed to create device cert: #{inspect(reason)}")
        Process.send_after(self(), :after_connect_amqp, 5000)
        {:noreply, state}
    end
  end

  def handle_info(:after_connect_amqp, %{key: _key, cert: _cert} = state) do
    FarmbotCore.Logger.debug(3, "Connected to NervesHub AMQP channel. Certs already loaded")
    send(self(), :connect_nerves_hub)
    {:noreply, state}
  end

  def handle_info(:connect_nerves_hub, %{key: nil, cert: nil} = state) do
    FarmbotCore.Logger.debug(3, "Can't connect to OTA Service. Certs not loaded")
    send(self(), :connect_amqp)
    {:noreply, state}
  end

  def handle_info(
        :connect_nerves_hub,
        %{key: _key, cert: _cert, probably_connected: false} = state
      ) do
    FarmbotCore.Logger.debug(3, "Starting OTA Service")
    do_restart_nerves_hub()
    FarmbotCore.Logger.debug(3, "OTA Service started")
    {:noreply, %{state | probably_connected: true}}
  end

  def handle_info(
        :connect_nerves_hub,
        %{key: _key, cert: _cert, probably_connected: true} = state
      ) do
    {:noreply, state}
  end

  # Confirmation sent by the broker after registering this process as a consumer
  def handle_info({:basic_consume_ok, _}, state) do
    {:noreply, state}
  end

  # Sent by the broker when the consumer is
  # unexpectedly cancelled (such as after a queue deletion)
  def handle_info({:basic_cancel, _}, state) do
    {:stop, :normal, state}
  end

  # Confirmation sent by the broker to the consumer process after a Basic.cancel
  def handle_info({:basic_cancel_ok, _}, state) do
    {:noreply, state}
  end

  def handle_info({:basic_deliver, payload, %{routing_key: key}}, state) do
    device = state.jwt.bot
    ["bot", ^device, "nerves_hub"] = String.split(key, ".")

    with {:ok, %{"cert" => base64_cert, "key" => base64_key}} <- JSON.decode(payload),
         {:ok, cert} <- Base.decode64(base64_cert),
         {:ok, key} <- Base.decode64(base64_key),
         :ok <- write_cert(cert),
         :ok <- write_key(key) do
      send(self(), :connect_nerves_hub)
      {:noreply, %{state | cert: cert, key: key}}
    else
      {:error, reason} ->
        FarmbotCore.Logger.error(1, "OTA Service failed to configure. #{inspect(reason)}")
        {:stop, reason, state}

      :error ->
        FarmbotCore.Logger.error(1, "OTA Service payload invalid. (base64)")
        {:stop, :invalid_payload, state}
    end
  end

  @impl GenServer
  def handle_cast({:handle_nerves_hub_error, error}, %{is_applying_update: true} = state) do
    FarmbotCore.Logger.error(1, "Error applying OTA: #{inspect(error)}")
    {:noreply, state}
  end

  def handle_cast({:handle_nerves_hub_error, error}, state) do
    FarmbotCore.Logger.debug(3, "Unexpected NervesHub error: #{inspect(error)}")
    {:noreply, state}
  end

  def handle_cast({:handle_nerves_hub_fwup_message, {:progress, percent}}, state) do
    _ = set_ota_progress(percent)
    {:noreply, state}
  end

  def handle_cast({:handle_nerves_hub_fwup_message, {:ok, _, _info}}, state) do
    _ = set_firmware_needs_flash()
    _ = set_ota_progress(100)
    {:noreply, state}
  end

  def handle_cast({:handle_nerves_hub_fwup_message, {:error, _, reason}}, state) do
    _ = set_ota_progress(100)
    FarmbotCore.Logger.error(1, "Error applying OTA: #{inspect(reason)}")
    {:noreply, state}
  end

  def handle_cast({:handle_nerves_hub_fwup_message, _}, state) do
    {:noreply, state}
  end

  @impl GenServer
  def handle_call({:handle_nerves_hub_update_available, %{"firmware_url" => url}}, _from, state) do
    case Asset.fbos_config(:os_auto_update) do
      true ->
        FarmbotCore.Logger.busy(1, "Applying OTA update")
        _ = set_firmware_needs_flash()
        {:reply, :apply, %{state | is_applying_update: true, firmware_url: url}}

      _ ->
        FarmbotCore.Logger.info(1, "New Farmbot OS is available!")
        {:reply, :ignore, %{state | firmware_url: url}}
    end
  end

  def handle_call({:handle_nerves_hub_update_available, _data}, _from, state) do
    FarmbotCore.Logger.busy(1, "Applying OTA update")
    {:reply, :apply, %{state | is_applying_update: true}}
  end

  def handle_call(:check_update, _from, state) do
    case NervesHub.HTTPClient.update() do
      {:ok, %{"data" => %{"update_available" => false}}} ->
        {:reply, nil, state}

      data ->
        FarmbotCore.Logger.busy(1, "Applying OTA update")
        _ = set_firmware_needs_flash()

        spawn_link(fn -> NervesHub.update() end)
        {:reply, data, %{state | is_applying_update: true}}
    end
  end

  defp get_jwt do
    token = Config.get_config_value(:string, "authorization", "token")

    if token do
      {:ok, jwt} = JWT.decode(token)
      {token, jwt}
    else
      {nil, nil}
    end
  end

  defp get_email do
    Config.get_config_value(:string, "authorization", "email")
  end

  defp open_connection(email, token, jwt) do
    case ConnectionWorker.open_connection(token, email, jwt.bot, jwt.mqtt, jwt.vhost) do
      {:ok, conn} ->
        Process.link(conn.pid)
        Process.monitor(conn.pid)
        {:ok, conn}

      err ->
        FarmbotCore.Logger.error(1, "Error connecting to AMQP: #{inspect(err)}")
        err
    end
  end

  defp filter_parens(""), do: nil
  defp filter_parens(data), do: data

  defp set_ota_progress(100) do
    FarmbotCore.Logger.success(1, "OTA Complete Going down for reboot")
    prog = %Percent{percent: 100, status: "complete"}

    if Process.whereis(BotState) do
      BotState.set_job_progress("FBOS_OTA", prog)
    end

    :ok
  end

  defp set_ota_progress(percent) do
    prog = %Percent{percent: percent}

    if Process.whereis(BotState) do
      BotState.set_job_progress("FBOS_OTA", prog)
    end

    :ok
  end

  def set_firmware_needs_flash() do
    Config.update_config_value(:bool, "settings", "firmware_needs_flash", true)
    Config.update_config_value(:bool, "settings", "firmware_needs_open", false)
    :ok
  end

  def detect_deployment_tags() do
    update_channel = detect_update_channel()
    ["application:#{Project.env()}", "channel:#{update_channel}"]
  end

  def detect_update_channel() do
    if Regex.match?(
         ~r/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)-rc(0|[1-9]\d*)+?/,
         Project.version()
       ) do
      "beta"
    else
      case Project.branch() do
        "master" -> "stable"
        branch -> branch
      end
    end
  end
end


loosen guards on HashRing exported functions

defmodule HashRing do
  use GenServer

  @driver "hash_ring_drv"
  @port_timeout 5000

  @hash_function_sha1 1
  @hash_function_md5 2
  @hash_functions [
    @hash_function_md5,
    @hash_function_sha1,
  ]

  @mode_normal 1
  @mode_libmemcached_compat 2

  def start_link(opts \\ []) do
    case :erl_ddll.load_driver(lib_path, @driver) do
      :ok ->
        {replicas, opts}  = Keyword.pop(opts, :replicas, 128)
        {hash_func, opts} = Keyword.pop(opts, :hash_func, :md5)
        GenServer.start_link(__MODULE__, [replicas, hash_func], opts)
      error ->
        error
    end
  end

  @spec add(pid, binary | atom) :: :ok | {:error, term}
  def add(ring, node) when is_atom(node), do: add(ring, to_string(node))
  def add(ring, node) when is_binary(node) do
    GenServer.call(ring, {:add, node})
  end

  @spec drop(pid, binary | atom) :: :ok | {:error, term}
  def drop(ring, node) when is_atom(node), do: add(ring, to_string(node))
  def drop(ring, node) when is_binary(node) do
    GenServer.call(ring, {:drop, node})
  end

  @spec find(pid, binary | atom) :: :ok | {:error, term}
  def find(ring, key) when is_atom(key), do: find(ring, to_string(key))
  def find(ring, key) when is_binary(key) do
    GenServer.call(ring, {:find, key})
  end

  @spec stop(pid) :: :ok
  def stop(ring) do
    GenServer.call(ring, :stop)
  end

  @spec set_mode(pid, atom) :: :ok | {:error, :term}
  def set_mode(ring, :normal) do
    GenServer.call(ring, {:set_mode, @mode_normal})
  end

  def set_mode(ring, :memcached) do
    GenServer.call(ring, {:set_mode, @mode_libmemcached_compat})
  end

  def set_mode(_, _) do
    {:error, :unsupported_mode}
  end

  #
  # Private API
  #

  defp lib_path do
    case :code.priv_dir(:hash_ring) do
      {:error, :bad_name} ->
        Path.join([Path.dirname(:code.which(:hash_ring)), "..", "priv"])
      path ->
        path
    end
  end

  defp create(port, replicas, hash_func) do
    command(port, <<1::size(8), replicas::size(32), hash_func::size(8)>>)
    receive do
      {port = port, {:data, <<index::size(32)>>}} ->
        {:ok, index}
    after
      @port_timeout ->
        {:error, :port_timeout}
    end
  end

  defp command(port, message) do
    send(port, {self, {:command, message}})
  end

  #
  # GenServer callbacks
  #

  def init([replicas, :md5]), do: init([replicas, @hash_function_md5])
  def init([replicas, :sha1]), do: init([replicas, @hash_function_sha1])
  def init([replicas, hash_func]) when hash_func in @hash_functions do
    port = :erlang.open_port({:spawn, @driver}, [:binary])
    case create(port, replicas, hash_func) do
      {:ok, index} ->
        {:ok, %{port: port, index: index}}
      error ->
        error
    end
  end

  def handle_call({:add, node}, _, %{port: port, index: index} = state) do
    node_size = size(node)
    command(port, <<3::size(8), index::size(32), node_size::size(32), node::binary>>)
    receive do
      {port = port, {:data, <<0::size(8)>>}} ->
        {:reply, :ok, state}
      {port = port, {:data, <<1::size(8)>>}} ->
        {:reply, {:error, :driver_error}, state}
    after
      @port_timeout ->
        {:reply, {:error, :port_timeout}, state}
    end
  end

  def handle_call({:drop, node}, _, %{port: port, index: index} = state) do
    node_size = size(node)
    command(port, <<4::size(8), index::size(32), node_size::size(32), node::binary>>)
    receive do
      {port = port, {:data, <<0::size(8)>>}} ->
        {:reply, :ok, state}
      {port = port, {:data, <<1::8>>}} ->
        {:reply, {:error, :driver_error}, state}
    after
      @port_timeout ->
        {:reply, {:error, :port_timeout}, state}
    end
  end

  def handle_call({:find, key}, _, %{port: port, index: index} = state) do
    key_size = size(key)
    send(port, {self, {:command, <<5::size(8), index::size(32), key_size::size(32), key::binary>>}})
    receive do
      {port = port, {:data, <<3::size(8)>>}} ->
        {:repy, {:error, :invalid_ring}, state}
      {port = port, {:data, <<2::size(8)>>}} ->
        {:reply, {:error, :node_not_found}}
      {port = port, {:data, <<1::size(8)>>}} ->
        {:reply, {:error, :no_nodes}, state}
      {port = port, {:data, <<node::binary>>}} ->
        {:reply, {:ok, node}, state}
    end
  end

  def handle_call({:set_mode, mode}, _, %{port: port, index: index} = state) do
    command(port, <<6::size(8), index::size(32), mode::size(8)>>)
    receive do
      {port = port, {:data, <<0::size(8)>>}} ->
        {:reply, :ok, state}
      {port = port, {:data, <<1::size(8)>>}} ->
        {:reply, {:error, :driver_error}, state}
    end
  end

  def handle_call(:stop, _, state) do
    {:stop, :normal, state}
  end

  def handle_info({:EXIT, port, _} = port_exit, %{port: port} = state) do
    {:stop, port_exit, state}
  end

  def terminate({:EXIT, port, _}, %{port: port}), do: :ok
  def terminate(_, %{port: port}) do
    send(port, {self, :close})
    receive do
      {port = port, :closed} ->
        :ok
    end
  end
end

defmodule HashRing do
  use GenServer

  @driver "hash_ring_drv"
  @port_timeout 5000

  @hash_function_sha1 1
  @hash_function_md5 2
  @hash_functions [
    @hash_function_md5,
    @hash_function_sha1,
  ]

  @mode_normal 1
  @mode_libmemcached_compat 2

  def start_link(opts \\ []) do
    case :erl_ddll.load_driver(lib_path, @driver) do
      :ok ->
        {replicas, opts}  = Keyword.pop(opts, :replicas, 128)
        {hash_func, opts} = Keyword.pop(opts, :hash_func, :md5)
        GenServer.start_link(__MODULE__, [replicas, hash_func], opts)
      error ->
        error
    end
  end

  @spec add(pid, binary | atom) :: :ok | {:error, term}
  def add(ring, node) when is_atom(node), do: add(ring, to_string(node))
  def add(ring, node) when is_pid(ring) do
    GenServer.call(ring, {:add, node})
  end

  @spec drop(pid, binary | atom) :: :ok | {:error, term}
  def drop(ring, node) when is_atom(node), do: add(ring, to_string(node))
  def drop(ring, node) when is_pid(ring) do
    GenServer.call(ring, {:drop, node})
  end

  @spec find(pid, binary | atom) :: :ok | {:error, term}
  def find(ring, key) when is_atom(key), do: find(ring, to_string(key))
  def find(ring, key) when is_pid(ring) do
    GenServer.call(ring, {:find, key})
  end

  @spec stop(pid) :: :ok
  def stop(ring) when is_pid(ring) do
    GenServer.call(ring, :stop)
  end

  @spec set_mode(pid, atom) :: :ok | {:error, :term}
  def set_mode(ring, :normal) when is_pid(ring) do
    GenServer.call(ring, {:set_mode, @mode_normal})
  end

  def set_mode(ring, :memcached) when is_pid(ring) do
    GenServer.call(ring, {:set_mode, @mode_libmemcached_compat})
  end

  def set_mode(_, _) do
    {:error, :unsupported_mode}
  end

  #
  # Private API
  #

  defp lib_path do
    case :code.priv_dir(:hash_ring) do
      {:error, :bad_name} ->
        Path.join([Path.dirname(:code.which(:hash_ring)), "..", "priv"])
      path ->
        path
    end
  end

  defp create(port, replicas, hash_func) do
    command(port, <<1::size(8), replicas::size(32), hash_func::size(8)>>)
    receive do
      {port = port, {:data, <<index::size(32)>>}} ->
        {:ok, index}
    after
      @port_timeout ->
        {:error, :port_timeout}
    end
  end

  defp command(port, message) do
    send(port, {self, {:command, message}})
  end

  #
  # GenServer callbacks
  #

  def init([replicas, :md5]), do: init([replicas, @hash_function_md5])
  def init([replicas, :sha1]), do: init([replicas, @hash_function_sha1])
  def init([replicas, hash_func]) when hash_func in @hash_functions do
    port = :erlang.open_port({:spawn, @driver}, [:binary])
    case create(port, replicas, hash_func) do
      {:ok, index} ->
        {:ok, %{port: port, index: index}}
      error ->
        error
    end
  end

  def handle_call({:add, node}, _, %{port: port, index: index} = state) do
    node_size = size(node)
    command(port, <<3::size(8), index::size(32), node_size::size(32), node::binary>>)
    receive do
      {port = port, {:data, <<0::size(8)>>}} ->
        {:reply, :ok, state}
      {port = port, {:data, <<1::size(8)>>}} ->
        {:reply, {:error, :driver_error}, state}
    after
      @port_timeout ->
        {:reply, {:error, :port_timeout}, state}
    end
  end

  def handle_call({:drop, node}, _, %{port: port, index: index} = state) do
    node_size = size(node)
    command(port, <<4::size(8), index::size(32), node_size::size(32), node::binary>>)
    receive do
      {port = port, {:data, <<0::size(8)>>}} ->
        {:reply, :ok, state}
      {port = port, {:data, <<1::8>>}} ->
        {:reply, {:error, :driver_error}, state}
    after
      @port_timeout ->
        {:reply, {:error, :port_timeout}, state}
    end
  end

  def handle_call({:find, key}, _, %{port: port, index: index} = state) do
    key_size = size(key)
    send(port, {self, {:command, <<5::size(8), index::size(32), key_size::size(32), key::binary>>}})
    receive do
      {port = port, {:data, <<3::size(8)>>}} ->
        {:repy, {:error, :invalid_ring}, state}
      {port = port, {:data, <<2::size(8)>>}} ->
        {:reply, {:error, :node_not_found}}
      {port = port, {:data, <<1::size(8)>>}} ->
        {:reply, {:error, :no_nodes}, state}
      {port = port, {:data, <<node::binary>>}} ->
        {:reply, {:ok, node}, state}
    end
  end

  def handle_call({:set_mode, mode}, _, %{port: port, index: index} = state) do
    command(port, <<6::size(8), index::size(32), mode::size(8)>>)
    receive do
      {port = port, {:data, <<0::size(8)>>}} ->
        {:reply, :ok, state}
      {port = port, {:data, <<1::size(8)>>}} ->
        {:reply, {:error, :driver_error}, state}
    end
  end

  def handle_call(:stop, _, state) do
    {:stop, :normal, state}
  end

  def handle_info({:EXIT, port, _} = port_exit, %{port: port} = state) do
    {:stop, port_exit, state}
  end

  def terminate({:EXIT, port, _}, %{port: port}), do: :ok
  def terminate(_, %{port: port}) do
    send(port, {self, :close})
    receive do
      {port = port, :closed} ->
        :ok
    end
  end
end


fix article sorting

fix article sorting

defmodule Scripture.Article do
  use Scripture.Web, :model

  schema "articles" do
    field :title, :string
    field :content, :string
    field :published, :boolean, virtual: true
    field :published_at, :utc_datetime
    has_many :comments, Scripture.Comment, on_delete: :delete_all

    timestamps()
  end

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:title, :content, :published])
    |> published_to_published_at()
    |> validate_required([:title, :content])
  end

  # https://blog.drewolson.org/composable-queries-ecto/
  def published(query \\ Scripture.Article) do
    from a in query,
      where: not(is_nil(a.published_at)),
      order_by: [desc: :published_at]
  end

  def with_comments(query \\ Scripture.Article) do
    from q in query, preload: [comments: :user]
  end

  def published?(article) do
    article.published_at != nil
  end

  def published_to_published_at(changeset, timestamp \\ DateTime.utc_now()) do
    with_published_at =
      case {changeset.changes[:published], changeset.data.published_at} do
        {true,  nil} -> put_change(changeset, :published_at, timestamp)
        {false, _  } -> put_change(changeset, :published_at, nil)
        _            -> changeset
      end

    delete_change(with_published_at, :published)
  end
end

defmodule Scripture.Article do
  use Scripture.Web, :model

  schema "articles" do
    field :title, :string
    field :content, :string
    field :published, :boolean, virtual: true
    field :published_at, :utc_datetime
    has_many :comments, Scripture.Comment, on_delete: :delete_all

    timestamps()
  end

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:title, :content, :published])
    |> published_to_published_at()
    |> validate_required([:title, :content])
  end

  # https://blog.drewolson.org/composable-queries-ecto/
  def published(query \\ Scripture.Article) do
    from a in query,
      where: not(is_nil(a.published_at)),
      order_by: [desc: :updated_at]
  end

  def with_comments(query \\ Scripture.Article) do
    from q in query, preload: [comments: :user]
  end

  def published?(article) do
    article.published_at != nil
  end

  def published_to_published_at(changeset, timestamp \\ DateTime.utc_now()) do
    with_published_at =
      case {changeset.changes[:published], changeset.data.published_at} do
        {true,  nil} -> put_change(changeset, :published_at, timestamp)
        {false, _  } -> put_change(changeset, :published_at, nil)
        _            -> changeset
      end

    delete_change(with_published_at, :published)
  end
end


Add test for additional_items

Add test for additional_items

defmodule Xema.ListTest do
  use ExUnit.Case, async: true

  doctest Xema.List

  import Xema

  describe "'list' schema" do
    setup do
      %{schema: xema(:list)}
    end

    test "type", %{schema: schema} do
      assert schema.type.as == :list
    end

    test "validate/2 with an empty list", %{schema: schema} do
      assert validate(schema, []) == :ok
    end

    test "validate/2 with an list of different types", %{schema: schema} do
      assert validate(schema, [1, "bla", 3.4]) == :ok
    end

    test "validate/2 with an invalid value", %{schema: schema} do
      expected = {:error, %{reason: :wrong_type, type: :list}}
      assert validate(schema, "not an array") == expected
    end

    test "is_valid?/2 with a valid value", %{schema: schema} do
      assert is_valid?(schema, [1])
    end

    test "is_valid?/2 with an invalid value", %{schema: schema} do
      refute is_valid?(schema, 42)
    end
  end

  describe "'list' schema with size" do
    setup do
      %{schema: xema(:list, min_items: 2, max_items: 3)}
    end

    test "validate/2 with too short list", %{schema: schema} do
      expected = {:error, %{reason: :too_less_items, min_items: 2}}
      assert validate(schema, [1]) == expected
    end

    test "validate/2 with proper list", %{schema: schema} do
      assert validate(schema, [1, 2]) == :ok
    end

    test "validate/2 with to long list", %{schema: schema} do
      expected = {:error, %{reason: :too_many_items, max_items: 3}}
      assert validate(schema, [1, 2, 3, 4]) == expected
    end
  end

  describe "'list' schema with typed items" do
    setup do
      %{
        integers: xema(:list, items: :integer),
        strings: xema(:list, items: :string)
      }
    end

    test "validate/2 integers with empty list", %{integers: schema} do
      assert validate(schema, []) == :ok
    end

    test "validate/2 integers with list of integers", %{integers: schema} do
      assert validate(schema, [1, 2]) == :ok
    end

    test "validate/2 integers with invalid list", %{integers: schema} do
      expected =
        {
          :error,
          %{
            reason: :invalid_item,
            at: 2,
            error: %{reason: :wrong_type, type: :integer}
          }
        }

      assert validate(schema, [1, 2, "foo"]) == expected
    end

    test "validate/2 strings with empty list", %{strings: schema} do
      assert validate(schema, []) == :ok
    end

    test "validate/2 strings with list of string", %{strings: schema} do
      assert validate(schema, ["foo"]) == :ok
    end

    test "validate/2 strings with invalid list", %{strings: schema} do
      expected =
        {
          :error,
          %{
            reason: :invalid_item,
            at: 0,
            error: %{reason: :wrong_type, type: :string}
          }
        }

      assert validate(schema, [1, 2, "foo"]) == expected
    end
  end

  describe "'list' schema with unique items" do
    setup do
      %{schema: xema(:list, unique_items: true)}
    end

    test "validate/2 with list of unique items", %{schema: schema} do
      assert validate(schema, [1, 2, 3]) == :ok
    end

    test "validate/2 with list of not unique items", %{schema: schema} do
      expected = {:error, %{reason: :not_unique}}
      assert validate(schema, [1, 2, 3, 3, 4]) == expected
    end
  end

  describe "'list' schema with tuple validation" do
    setup do
      %{
        schema:
          xema(
            :list,
            items: [
              {:string, min_length: 3},
              {:number, minimum: 10}
            ]
          )
      }
    end

    test "validate/2 with valid values", %{schema: schema} do
      assert validate(schema, ["foo", 42]) == :ok
    end

    test "validate/2 with invalid values", %{schema: schema} do
      assert validate(schema, ["foo", "bar"]) ==
               {:error, %{
                 reason: :invalid_item,
                 at: 1,
                 error: %{reason: :wrong_type, type: :number}
               }}

      assert validate(schema, ["x", 33]) ==
               {:error, %{
                 reason: :invalid_item,
                 at: 0,
                 error: %{reason: :too_short, min_length: 3}
               }}
    end

    test "validate/2 with additional item", %{schema: schema} do
      assert validate(schema, ["foo", 42, "add"]) == :ok
    end

    test "validate/2 with missing item", %{schema: schema} do
      assert validate(schema, ["foo"]) == :ok
    end
  end

  describe "'list' schema with tuple validation without addtional items" do
    setup do
      %{
        schema:
          xema(
            :list,
            additional_items: false,
            items: [
              {:string, min_length: 3},
              {:number, minimum: 10}
            ]
          )
      }
    end

    test "validate/2 with additional item", %{schema: schema} do
      expected = {:error, %{at: 2, reason: :additional_item}}
      assert validate(schema, ["foo", 42, "add"]) == expected
    end
  end

  describe "list schema with with specific additional items" do
    setup do
      %{
        schema:
          xema(
            :list,
            additional_items: :string,
            items: [
              {:number, minimum: 10}
            ]
          )
      }
    end

    test "validate/2 with valid additional item", %{schema: schema} do
      assert validate(schema, [11, "twelve"]) == :ok
    end

    test "validate/2 with invalid additional item", %{schema: schema} do
      IO.puts("ha")
      assert validate(schema, [11, 12]) == :error
    end
  end
end

defmodule Xema.ListTest do
  use ExUnit.Case, async: true

  doctest Xema.List

  import Xema

  describe "'list' schema" do
    setup do
      %{schema: xema(:list)}
    end

    test "type", %{schema: schema} do
      assert schema.type.as == :list
    end

    test "validate/2 with an empty list", %{schema: schema} do
      assert validate(schema, []) == :ok
    end

    test "validate/2 with an list of different types", %{schema: schema} do
      assert validate(schema, [1, "bla", 3.4]) == :ok
    end

    test "validate/2 with an invalid value", %{schema: schema} do
      expected = {:error, %{reason: :wrong_type, type: :list}}
      assert validate(schema, "not an array") == expected
    end

    test "is_valid?/2 with a valid value", %{schema: schema} do
      assert is_valid?(schema, [1])
    end

    test "is_valid?/2 with an invalid value", %{schema: schema} do
      refute is_valid?(schema, 42)
    end
  end

  describe "'list' schema with size" do
    setup do
      %{schema: xema(:list, min_items: 2, max_items: 3)}
    end

    test "validate/2 with too short list", %{schema: schema} do
      expected = {:error, %{reason: :too_less_items, min_items: 2}}
      assert validate(schema, [1]) == expected
    end

    test "validate/2 with proper list", %{schema: schema} do
      assert validate(schema, [1, 2]) == :ok
    end

    test "validate/2 with to long list", %{schema: schema} do
      expected = {:error, %{reason: :too_many_items, max_items: 3}}
      assert validate(schema, [1, 2, 3, 4]) == expected
    end
  end

  describe "'list' schema with typed items" do
    setup do
      %{
        integers: xema(:list, items: :integer),
        strings: xema(:list, items: :string)
      }
    end

    test "validate/2 integers with empty list", %{integers: schema} do
      assert validate(schema, []) == :ok
    end

    test "validate/2 integers with list of integers", %{integers: schema} do
      assert validate(schema, [1, 2]) == :ok
    end

    test "validate/2 integers with invalid list", %{integers: schema} do
      expected =
        {
          :error,
          %{
            reason: :invalid_item,
            at: 2,
            error: %{reason: :wrong_type, type: :integer}
          }
        }

      assert validate(schema, [1, 2, "foo"]) == expected
    end

    test "validate/2 strings with empty list", %{strings: schema} do
      assert validate(schema, []) == :ok
    end

    test "validate/2 strings with list of string", %{strings: schema} do
      assert validate(schema, ["foo"]) == :ok
    end

    test "validate/2 strings with invalid list", %{strings: schema} do
      expected =
        {
          :error,
          %{
            reason: :invalid_item,
            at: 0,
            error: %{reason: :wrong_type, type: :string}
          }
        }

      assert validate(schema, [1, 2, "foo"]) == expected
    end
  end

  describe "'list' schema with unique items" do
    setup do
      %{schema: xema(:list, unique_items: true)}
    end

    test "validate/2 with list of unique items", %{schema: schema} do
      assert validate(schema, [1, 2, 3]) == :ok
    end

    test "validate/2 with list of not unique items", %{schema: schema} do
      expected = {:error, %{reason: :not_unique}}
      assert validate(schema, [1, 2, 3, 3, 4]) == expected
    end
  end

  describe "'list' schema with tuple validation" do
    setup do
      %{
        schema:
          xema(
            :list,
            items: [
              {:string, min_length: 3},
              {:number, minimum: 10}
            ]
          )
      }
    end

    test "validate/2 with valid values", %{schema: schema} do
      assert validate(schema, ["foo", 42]) == :ok
    end

    test "validate/2 with invalid values", %{schema: schema} do
      assert validate(schema, ["foo", "bar"]) ==
               {:error, %{
                 reason: :invalid_item,
                 at: 1,
                 error: %{reason: :wrong_type, type: :number}
               }}

      assert validate(schema, ["x", 33]) ==
               {:error, %{
                 reason: :invalid_item,
                 at: 0,
                 error: %{reason: :too_short, min_length: 3}
               }}
    end

    test "validate/2 with additional item", %{schema: schema} do
      assert validate(schema, ["foo", 42, "add"]) == :ok
    end

    test "validate/2 with missing item", %{schema: schema} do
      assert validate(schema, ["foo"]) == :ok
    end
  end

  describe "'list' schema with tuple validation without addtional items" do
    setup do
      %{
        schema:
          xema(
            :list,
            additional_items: false,
            items: [
              {:string, min_length: 3},
              {:number, minimum: 10}
            ]
          )
      }
    end

    test "validate/2 with additional item", %{schema: schema} do
      expected = {:error, %{at: 2, reason: :additional_item}}
      assert validate(schema, ["foo", 42, "add"]) == expected
    end
  end
end


Use Collectable protocol with an empty map

defmodule Comprehensions do
  use Koans

  @intro "A comprehension is made of three parts: generators, filters, and collectibles. We will look at how these interact with eachother"

  koan "The generator, `n <- [1, 2, 3, 4]`, is providing the values for our comprehension" do
    assert (for n <- [1, 2, 3, 4], do: n * n) == ___
  end

  koan "Any enumerable can be a generator" do
    assert (for n <- 1..4, do: n * n) == ___
  end

  koan "A generator specifies how to extract values from a collection" do
    collection = [["Hello","World"], ["Apple", "Pie"]]
    assert (for [a, b] <- collection, do: "#{a} #{b}") == ___
  end

  koan "You can use multiple generators at once" do
    assert (for x <- ["little", "big"], y <- ["dogs", "cats"], do: "#{x} #{y}") == ___
  end

  koan "Use a filter to reduce your work" do
    assert (for n <- [1, 2, 3, 4, 5, 6], n > 3, do: n) == ___
  end

  koan "Add the result of a comprehension to an existing collection" do
    collection = for x <- ["Pecan", "Pumpkin"], into: %{}, do: {x, "#{x} Pie"}
    assert collection == ___
  end

end

defmodule Comprehensions do
  use Koans

  @intro "A comprehension is made of three parts: generators, filters, and collectibles. We will look at how these interact with eachother"

  koan "The generator, `n <- [1, 2, 3, 4]`, is providing the values for our comprehension" do
    assert (for n <- [1, 2, 3, 4], do: n * n) == ___
  end

  koan "Any enumerable can be a generator" do
    assert (for n <- 1..4, do: n * n) == ___
  end

  koan "A generator specifies how to extract values from a collection" do
    collection = [["Hello","World"], ["Apple", "Pie"]]
    assert (for [a, b] <- collection, do: "#{a} #{b}") == ___
  end

  koan "You can use multiple generators at once" do
    assert (for x <- ["little", "big"], y <- ["dogs", "cats"], do: "#{x} #{y}") == ___
  end

  koan "Use a filter to reduce your work" do
    assert (for n <- [1, 2, 3, 4, 5, 6], n > 3, do: n) == ___
  end

  koan "Add the result of a comprehension to an existing collection" do
    collection = ["Apple Pie"]
    collection = for x <- ["Pecan", "Pumpkin"], into: collection, do: "#{x} Pie"
    assert collection == ___
  end

end


Support redirects as a macro

Support redirects as a macro

defmodule Trot.Router do
  alias Plug.Conn.Status
  require Logger

  @doc false
  defmacro __using__(_opts) do
    quote do
      import Trot.Router
      use Plug.Builder

      plug Plug.Logger
      plug :match
      plug :dispatch

      defp match(conn, _opts) do
        Plug.Conn.put_private(conn,
          :trot_route,
          do_match(conn.method, Enum.map(conn.path_info, &URI.decode/1), conn.host))
      end

      defp dispatch(%Plug.Conn{assigns: assigns} = conn, _opts) do
        Map.get(conn.private, :trot_route).(conn)
      end

      def call(conn, opts) do
        super(conn, opts)
        |> Trot.not_found(opts)
        |> assign(:called_all_plugs, true)
      end

      @before_compile Trot.Router
    end
  end

  defmacro __before_compile__(env) do
    quote do
      defp do_match(_method, _path, _host) do
        fn(conn) -> conn end
      end
    end
  end

  @doc ~S"""
  Encodes HTTP responses as appropriate and passes them to Plug.Conn.

  ## Examples

      # Sets status code to 200 with an empty body
      get "/" do
        200
      end

      # Returns an empty body with a status code of 404
      get "/bad" do
        :bad_request
      end

      # Sets the status code to 200 with a text body
      get "/text" do
        "Thank you for your question."
      end

      # Sets the status code to 201 with a text body
      get "/text/body" do
        {201, "Thank you for your question."}
      end

      # Sets status code to 200 with a JSON-encoded body
      get "/json" do
        %{"hyper" => "social"}
      end

      # Sets the status code to 201 with a JSON-encoded body
      get "/json/code" do
        {201, %{"hyper" => "social"}}
      end

      # Set the response manually as when using Plug directly
      get "/conn" do
        send_resp(conn, 200, "optimal tip-to-tip efficiency")
      end

      # Pattern match part of the path into a variable
      get "/presenter/:name" do
        "The presenter is #{name}"
      end

      # Redirect the incoming request
      get "/redirect" do
        {:redirect, "/text/body"}
      end
  """
  def make_response(%Plug.Conn{state: :set}, _conn) do
    raise ArgumentError, message: "conn must be sent before being returned"
  end
  def make_response(conn = %Plug.Conn{}, _conn), do: conn
  def make_response({:redirect, to}, conn), do: do_redirect(to, conn)
  def make_response(code, conn) when is_number(code) do
    Plug.Conn.send_resp(conn, code, "")
  end
  def make_response(code, conn) when is_atom(code) do
    code = Status.code(code)
    Plug.Conn.send_resp(conn, code, "")
  end
  def make_response({code, body}, conn) when is_number(code) and is_binary(body) do
    Plug.Conn.send_resp(conn, code, body)
  end
  def make_response({code, body}, conn) when is_number(code) do
    body = Poison.encode!(body)
    Plug.Conn.send_resp(conn, code, body)
  end
  def make_response(body, conn) when is_binary(body) do
    Plug.Conn.send_resp(conn, Status.code(:ok), body)
  end
  def make_response(body, conn) do
    body = Poison.encode!(body)
    Plug.Conn.send_resp(conn, Status.code(:ok), body)
  end

  @doc """
  Redirect the request to another location.
  """
  def do_redirect(path, conn) when is_binary(path) do
    Logger.info "Redirecting to #{path}"
    URI.parse(path) |> do_redirect(conn)
  end
  def do_redirect(uri = %URI{}, conn) do
    conn
    |> Plug.Conn.put_resp_header("Location", to_string(uri))
    |> Plug.Conn.send_resp(Status.code(:temporary_redirect), "")
  end

  defmacro get(path, options \\ [], do: body), do: compile(:get, path, options, body)
  defmacro post(path, options \\ [], do: body), do: compile(:post, path, options, body)
  defmacro put(path, options \\ [], do: body), do: compile(:put, path, options, body)
  defmacro patch(path, options \\ [], do: body), do: compile(:patch, path, options, body)
  defmacro delete(path, options \\ [], do: body), do: compile(:delete, path, options, body)
  defmacro options(path, options \\ [], do: body), do: compile(:options, path, options, body)

  defmacro static(at, from) do
    quote do
      @plugs {Plug.Static, [at: unquote(at), from: unquote(from)], true}
    end
  end

  defmacro redirect(from, to) do
    body = quote do: {:redirect, unquote(to)}
    compile(:get, from, [], body)
  end

  # Entry point for both forward and match that is actually
  # responsible to compile the route.
  defp compile(method, expr, options, body) do
    {path, guards} = extract_path_and_guards(expr)
    options = sanitize_options(options)

    quote bind_quoted: [method: method,
                        path: path,
                        options: options,
                        guards: Macro.escape(guards, unquote: true),
                        body: Macro.escape(body, unquote: true)] do
      {method, match, host, guards} = Plug.Router.__route__(method, path, guards, options)

      defp do_match(unquote(method), unquote(match), unquote(host)) when unquote(guards) do
        fn var!(conn) -> unquote(body) |> Trot.Router.make_response(var!(conn)) end
      end
    end
  end

  defp sanitize_options(options), do: Enum.map(options, &default_keyword/1)

  defp default_keyword(item = {_key, _value}), do: item
  defp default_keyword(key) when is_atom(key), do: {key, true}

  # Extract the path and guards from the path.
  defp extract_path_and_guards({:when, _, [path, guards]}), do: {extract_path(path), guards}
  defp extract_path_and_guards(path), do: {extract_path(path), true}

  defp extract_path({:_, _, var}) when is_atom(var), do: "/*_path"
  defp extract_path(path), do: path
end

defmodule Trot.Router do
  alias Plug.Conn.Status
  require Logger

  @doc false
  defmacro __using__(_opts) do
    quote do
      import Trot.Router
      use Plug.Builder

      plug Plug.Logger
      plug :match
      plug :dispatch

      defp match(conn, _opts) do
        Plug.Conn.put_private(conn,
          :trot_route,
          do_match(conn.method, Enum.map(conn.path_info, &URI.decode/1), conn.host))
      end

      defp dispatch(%Plug.Conn{assigns: assigns} = conn, _opts) do
        Map.get(conn.private, :trot_route).(conn)
      end

      def call(conn, opts) do
        super(conn, opts)
        |> Trot.not_found(opts)
        |> assign(:called_all_plugs, true)
      end

      @before_compile Trot.Router
    end
  end

  defmacro __before_compile__(env) do
    quote do
      defp do_match(_method, _path, _host) do
        fn(conn) -> conn end
      end
    end
  end

  @doc ~S"""
  Encodes HTTP responses as appropriate and passes them to Plug.Conn.

  ## Examples

      # Sets status code to 200 with an empty body
      get "/" do
        200
      end

      # Returns an empty body with a status code of 404
      get "/bad" do
        :bad_request
      end

      # Sets the status code to 200 with a text body
      get "/text" do
        "Thank you for your question."
      end

      # Sets the status code to 201 with a text body
      get "/text/body" do
        {201, "Thank you for your question."}
      end

      # Sets status code to 200 with a JSON-encoded body
      get "/json" do
        %{"hyper" => "social"}
      end

      # Sets the status code to 201 with a JSON-encoded body
      get "/json/code" do
        {201, %{"hyper" => "social"}}
      end

      # Set the response manually as when using Plug directly
      get "/conn" do
        send_resp(conn, 200, "optimal tip-to-tip efficiency")
      end

      # Pattern match part of the path into a variable
      get "/presenter/:name" do
        "The presenter is #{name}"
      end

      # Redirect the incoming request
      get "/redirect" do
        {:redirect, "/text/body"}
      end
  """
  def make_response(%Plug.Conn{state: :set}, _conn) do
    raise ArgumentError, message: "conn must be sent before being returned"
  end
  def make_response(conn = %Plug.Conn{}, _conn), do: conn
  def make_response({:redirect, to}, conn), do: redirect(to, conn)
  def make_response(code, conn) when is_number(code) do
    Plug.Conn.send_resp(conn, code, "")
  end
  def make_response(code, conn) when is_atom(code) do
    code = Status.code(code)
    Plug.Conn.send_resp(conn, code, "")
  end
  def make_response({code, body}, conn) when is_number(code) and is_binary(body) do
    Plug.Conn.send_resp(conn, code, body)
  end
  def make_response({code, body}, conn) when is_number(code) do
    body = Poison.encode!(body)
    Plug.Conn.send_resp(conn, code, body)
  end
  def make_response(body, conn) when is_binary(body) do
    Plug.Conn.send_resp(conn, Status.code(:ok), body)
  end
  def make_response(body, conn) do
    body = Poison.encode!(body)
    Plug.Conn.send_resp(conn, Status.code(:ok), body)
  end

  @doc """
  Redirect the request to another location.
  """
  def redirect(path, conn) when is_binary(path) do
    Logger.info "Redirecting to #{path}"
    URI.parse(path) |> redirect(conn)
  end
  def redirect(uri = %URI{}, conn) do
    conn
    |> Plug.Conn.put_resp_header("Location", to_string(uri))
    |> Plug.Conn.send_resp(Status.code(:temporary_redirect), "")
  end

  defmacro get(path, options \\ [], do: body), do: compile(:get, path, options, body)
  defmacro post(path, options \\ [], do: body), do: compile(:post, path, options, body)
  defmacro put(path, options \\ [], do: body), do: compile(:put, path, options, body)
  defmacro patch(path, options \\ [], do: body), do: compile(:patch, path, options, body)
  defmacro delete(path, options \\ [], do: body), do: compile(:delete, path, options, body)
  defmacro options(path, options \\ [], do: body), do: compile(:options, path, options, body)

  defmacro static(at, from) do
    quote do
      @plugs {Plug.Static, [at: unquote(at), from: unquote(from)], true}
    end
  end

  # Entry point for both forward and match that is actually
  # responsible to compile the route.
  defp compile(method, expr, options, body) do
    {path, guards} = extract_path_and_guards(expr)
    options = sanitize_options(options)

    quote bind_quoted: [method: method,
                        path: path,
                        options: options,
                        guards: Macro.escape(guards, unquote: true),
                        body: Macro.escape(body, unquote: true)] do
      {method, match, host, guards} = Plug.Router.__route__(method, path, guards, options)

      defp do_match(unquote(method), unquote(match), unquote(host)) when unquote(guards) do
        fn var!(conn) -> unquote(body) |> Trot.Router.make_response(var!(conn)) end
      end
    end
  end

  defp sanitize_options(options), do: Enum.map(options, &default_keyword/1)

  defp default_keyword(item = {_key, _value}), do: item
  defp default_keyword(key) when is_atom(key), do: {key, true}

  # Extract the path and guards from the path.
  defp extract_path_and_guards({:when, _, [path, guards]}), do: {extract_path(path), guards}
  defp extract_path_and_guards(path), do: {extract_path(path), true}

  defp extract_path({:_, _, var}) when is_atom(var), do: "/*_path"
  defp extract_path(path), do: path
end


Add secret_key_base and ecto config.

Add secret_key_base and ecto config.

This fixes 2 errors during the phoenix.server run:
* (ArgumentError) cookie store expects conn.secret_key_base to be set
* (Postgrex.Error) FATAL (invalid_catalog_name): database "ueberauth_example_dev" does not exist

# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
#
# This configuration file is loaded before any dependency and
# is restricted to this project.
use Mix.Config

# Configures the endpoint
config :ueberauth_example, UeberauthExample.Endpoint,
  url: [host: "localhost"],
  root: Path.dirname(__DIR__),
  render_errors: [accepts: ~w(html json)],
  secret_key_base: System.get_env("SECRET_KEY_BASE"),
  pubsub: [name: UeberauthExample.PubSub,
           adapter: Phoenix.PubSub.PG2]

# Configures Elixir's Logger
config :logger, :console,
  format: "$time $metadata[$level] $message\n",
  metadata: [:request_id]

# Configure the Ecto Repos
config :ueberauth_example, ecto_repos: [UeberauthExample.Repo]

# 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"

# Configure phoenix generators
config :phoenix, :generators,
  migration: true,
  binary_id: false

config :ueberauth, Ueberauth,
  providers: [
    facebook: { Ueberauth.Strategy.Facebook, [] },
    github: { Ueberauth.Strategy.Github, [] },
    google: { Ueberauth.Strategy.Google, [] },
    identity: { Ueberauth.Strategy.Identity, [
        callback_methods: ["POST"],
        uid_field: :username,
        nickname_field: :username,
      ] },
    slack: { Ueberauth.Strategy.Slack, [] },
    twitter: { Ueberauth.Strategy.Twitter, []}
  ]

config :ueberauth, Ueberauth.Strategy.Facebook.OAuth,
  client_id: System.get_env("FACEBOOK_APP_ID"),
  client_secret: System.get_env("FACEBOOK_APP_SECRET"),
  redirect_uri: System.get_env("FACEBOOK_REDIRECT_URI")

config :ueberauth, Ueberauth.Strategy.Github.OAuth,
  client_id: System.get_env("GITHUB_CLIENT_ID"),
  client_secret: System.get_env("GITHUB_CLIENT_SECRET")

config :ueberauth, Ueberauth.Strategy.Google.OAuth,
  client_id: System.get_env("GOOGLE_CLIENT_ID"),
  client_secret: System.get_env("GOOGLE_CLIENT_SECRET"),
  redirect_uri: System.get_env("GOOGLE_REDIRECT_URI")

config :ueberauth, Ueberauth.Strategy.Slack.OAuth,
  client_id: System.get_env("SLACK_CLIENT_ID"),
  client_secret: System.get_env("SLACK_CLIENT_SECRET")

config :ueberauth, Ueberauth.Strategy.Twitter.OAuth,
  consumer_key: System.get_env("TWITTER_CONSUMER_KEY"),
  consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET")

config :dogma,
  rule_set: Dogma.RuleSet.All

# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
#
# This configuration file is loaded before any dependency and
# is restricted to this project.
use Mix.Config

# Configures the endpoint
config :ueberauth_example, UeberauthExample.Endpoint,
  url: [host: "localhost"],
  root: Path.dirname(__DIR__),
  render_errors: [accepts: ~w(html json)],
  pubsub: [name: UeberauthExample.PubSub,
           adapter: Phoenix.PubSub.PG2]

# Configures Elixir's Logger
config :logger, :console,
  format: "$time $metadata[$level] $message\n",
  metadata: [:request_id]

# 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"

# Configure phoenix generators
config :phoenix, :generators,
  migration: true,
  binary_id: false

config :ueberauth, Ueberauth,
  providers: [
    facebook: { Ueberauth.Strategy.Facebook, [] },
    github: { Ueberauth.Strategy.Github, [] },
    google: { Ueberauth.Strategy.Google, [] },
    identity: { Ueberauth.Strategy.Identity, [
        callback_methods: ["POST"],
        uid_field: :username,
        nickname_field: :username,
      ] },
    slack: { Ueberauth.Strategy.Slack, [] },
    twitter: { Ueberauth.Strategy.Twitter, []}
  ]

config :ueberauth, Ueberauth.Strategy.Facebook.OAuth,
  client_id: System.get_env("FACEBOOK_APP_ID"),
  client_secret: System.get_env("FACEBOOK_APP_SECRET"),
  redirect_uri: System.get_env("FACEBOOK_REDIRECT_URI")

config :ueberauth, Ueberauth.Strategy.Github.OAuth,
  client_id: System.get_env("GITHUB_CLIENT_ID"),
  client_secret: System.get_env("GITHUB_CLIENT_SECRET")

config :ueberauth, Ueberauth.Strategy.Google.OAuth,
  client_id: System.get_env("GOOGLE_CLIENT_ID"),
  client_secret: System.get_env("GOOGLE_CLIENT_SECRET"),
  redirect_uri: System.get_env("GOOGLE_REDIRECT_URI")

config :ueberauth, Ueberauth.Strategy.Slack.OAuth,
  client_id: System.get_env("SLACK_CLIENT_ID"),
  client_secret: System.get_env("SLACK_CLIENT_SECRET")

config :ueberauth, Ueberauth.Strategy.Twitter.OAuth,
  consumer_key: System.get_env("TWITTER_CONSUMER_KEY"),
  consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET")

config :dogma,
  rule_set: Dogma.RuleSet.All


Use Guardian (WIP)

Use Guardian (WIP)

defmodule Core.Auth do
  import Plug.Conn
  import Comeonin.Bcrypt, only: [checkpw: 2]

  alias Core.User

  def init(opts) do
    Keyword.fetch!(opts, :repo)
  end

  @doc """
  Load the User object of a person with active session
  """
  def call(conn, repo) do
    access_token = get_req_header(conn, "authorization")
    IO.puts(access_token)

    case access_token do
      "12345" ->
        user = repo.get(User, 5)
        assign(conn, :current_user, user)
      _ ->
        assign(conn, :current_user, nil)
    end
  end

  @doc """
  Try to authenticate an user and if success, starts a session for him
  """
  def login(conn, email, pass, opts) do
    repo = Keyword.fetch!(opts, :repo)
    user = repo.get_by(User, email: email)

    cond do
      user && checkpw(pass, user.crypted_password) ->
        {:ok, create_session(conn, user)}
      true ->
        {:error, :unauthorized, conn}
    end
  end

  defp create_session(conn, user) do
    new_conn = Guardian.Plug.api_sign_in(conn, user)

    session = %{
      user: user,
      jwt: Guardian.Plug.current_token(new_conn),
      exp: Guardian.Plug.claims(new_conn) |> Map.get("exp")
    }

    conn
    |> assign(:current_session, session)
  end
end

defmodule Core.Auth do
  import Plug.Conn
  import Comeonin.Bcrypt, only: [checkpw: 2]

  alias Core.User

  def init(opts) do
    Keyword.fetch!(opts, :repo)
  end

  @doc """
  Load the User object of a person with active session
  """
  def call(conn, repo) do
    access_token = get_req_header(conn, "authorization")
    IO.puts(access_token)

    case access_token do
      "12345" ->
        user = repo.get(User, 5)
        assign(conn, :current_user, user)
      _ ->
        assign(conn, :current_user, nil)
    end
  end

  @doc """
  Try to authenticate an user and if success, starts a session for him
  """
  def login(conn, email, pass, opts) do
    repo = Keyword.fetch!(opts, :repo)
    user = repo.get_by(User, email: email)

    cond do
      user && checkpw(pass, user.crypted_password) ->
        {:ok, create_session(conn, user)}
      true ->
        {:error, :unauthorized, conn}
    end
  end

  defp create_session(conn, user) do
    session = %{
      access_token: "12345",
      token_type: "Bearer",
      expires_in: 3600*24*60
    }
    conn
    |> assign(:current_session, session)
  end
end