Refactor Slugged Routes Controller tests with helpers

defmodule CodeCorps.SluggedRouteControllerTest do
  use CodeCorps.ApiCase, resource_name: :slugged_route

  @valid_attrs %{organization_id: 42, slug: "some content", user_id: 42}
  @invalid_attrs %{}

  test "shows chosen resource", %{conn: conn} do
    slug = "test-slug"
    slugged_route = insert(:slugged_route, slug: slug)
    conn
    |> get("/#{slug}")
    |> json_response(200)
    |> Map.get("data")
    |> assert_result_id(slugged_route.id)
  end

  test "is case insensitive", %{conn: conn} do
    slug = "test"
    insert(:slugged_route, slug: slug)

    assert conn |> get("/test") |> json_response(200)
    assert conn |> get("/tEst") |> json_response(200)
  end

  test "renders 404 when id is nonexistent", %{conn: conn} do
    assert conn |> request_show(:not_found) |> json_response(404)
  end
end

defmodule CodeCorps.SluggedRouteControllerTest do
  use CodeCorps.ApiCase

  @valid_attrs %{organization_id: 42, slug: "some content", user_id: 42}
  @invalid_attrs %{}

  test "shows chosen resource", %{conn: conn} do
    slug = "test-slug"
    slugged_route = insert(:slugged_route, slug: slug)
    conn = get conn, "/#{slug}"
    data = json_response(conn, 200)["data"]
    assert data["id"] == "#{slugged_route.id}"
    assert data["type"] == "slugged-route"
    assert data["attributes"]["slug"] == slugged_route.slug
    assert data["attributes"]["organization_id"] == slugged_route.organization_id
    assert data["attributes"]["user_id"] == slugged_route.user_id
  end

  test "is case insensitive", %{conn: conn} do
    slug = "test"
    insert(:slugged_route, slug: slug)

    assert conn |> get("/test") |> json_response(200)
    assert conn |> get("/tEst") |> json_response(200)
  end

  test "does not show resource and instead throw error when id is nonexistent", %{conn: conn} do
    path = conn |> slugged_route_path(:show, -1)
    assert conn |> get(path) |> json_response(:not_found)
  end
end


Update USDA url to https since http endpoint returns 301 now.

HTTPoison.start
case HTTPoison.get("https://www.usda.gov/data.json") do
  {:ok, %HTTPoison.Response{body: body, status_code: 200}} -> File.write!("profile/usda.json", body)
end
case HTTPoison.get("https://edg.epa.gov/data.json") do
  {:ok, %HTTPoison.Response{body: body, status_code: 200}} -> File.write!("profile/edg.json", body)
end

HTTPoison.start
case HTTPoison.get("http://www.usda.gov/data.json") do
  {:ok, %HTTPoison.Response{body: body, status_code: 200}} -> File.write!("profile/usda.json", body)
end
case HTTPoison.get("https://edg.epa.gov/data.json") do
  {:ok, %HTTPoison.Response{body: body, status_code: 200}} -> File.write!("profile/edg.json", body)
end


Run the code formatter on Mix.Tasks.Compile.Erlang (#6856)

defmodule Mix.Tasks.Compile.Erlang do
  use Mix.Task.Compiler
  import Mix.Compilers.Erlang

  @recursive true
  @manifest ".compile.erlang"
  @switches [force: :boolean, all_warnings: :boolean]

  @moduledoc """
  Compiles Erlang source files.

  When this task runs, it will first check the modification times of
  all files to be compiled and if they haven't been
  changed since the last compilation, it will not compile
  them. If any of them have changed, it compiles
  everything.

  For this reason, the task touches your `:compile_path`
  directory and sets the modification time to the current
  time and date at the end of each compilation. You can
  force compilation regardless of modification times by passing
  the `--force` option.

  ## Command line options

    * `--force` - forces compilation regardless of modification times

    * `--all-warnings` - prints warnings even from files that do not need to be
      recompiled

  ## Configuration

    * `ERL_COMPILER_OPTIONS` - can be used to give default compile options.
      The value must be a valid Erlang term. If the value is a list, it will
      be used as is. If it is not a list, it will be put into a list.

    * `:erlc_paths` - directories to find source files.
      Defaults to `["src"]`.

    * `:erlc_include_path` - directory for adding include files.
      Defaults to `"include"`.

    * `:erlc_options` - compilation options that apply to Erlang's
      compiler. Defaults to `[:debug_info]`.

      For a complete list of options, see `:compile.file/2`.

  For example, to configure the `erlc_options` for your Erlang project you
  may run:

      erlc_options: [:debug_info, {:i, 'path/to/include'}]

  """

  @doc """
  Runs this task.
  """
  def run(args) do
    {opts, _, _} = OptionParser.parse(args, switches: @switches)
    project = Mix.Project.config()
    source_paths = project[:erlc_paths]
    Mix.Compilers.Erlang.assert_valid_erlc_paths(source_paths)
    files = Mix.Utils.extract_files(source_paths, [:erl])
    do_run(files, opts, project, source_paths)
  end

  defp do_run([], _, _, _), do: :noop

  defp do_run(files, opts, project, source_paths) do
    include_path = to_erl_file(project[:erlc_include_path])
    compile_path = to_erl_file(Mix.Project.compile_path(project))

    erlc_options = project[:erlc_options] || []

    unless is_list(erlc_options) do
      Mix.raise(":erlc_options should be a list of options, got: #{inspect(erlc_options)}")
    end

    erlc_options = erlc_options ++ [:return, :report, outdir: compile_path, i: include_path]

    erlc_options =
      Enum.map(erlc_options, fn
        {kind, dir} when kind in [:i, :outdir] -> {kind, to_erl_file(dir)}
        opt -> opt
      end)

    compile_path = Path.relative_to(compile_path, File.cwd!())

    tuples =
      files
      |> scan_sources(include_path, source_paths)
      |> sort_dependencies()
      |> Enum.map(&annotate_target(&1, compile_path, opts[:force]))

    Mix.Compilers.Erlang.compile(manifest(), tuples, opts, fn input, _output ->
      # We're purging the module because a previous compiler (e.g. Phoenix)
      # might have already loaded the previous version of it.
      module = input |> Path.basename(".erl") |> String.to_atom()
      :code.purge(module)
      :code.delete(module)

      file = to_erl_file(Path.rootname(input, ".erl"))

      case :compile.file(file, erlc_options) do
        {:error, :badarg} ->
          message =
            "Compiling Erlang #{inspect(file)} failed with ArgumentError, probably because of invalid :erlc_options"

          Mix.raise(message)

        result ->
          result
      end
    end)
  end

  @doc """
  Returns Erlang manifests.
  """
  def manifests, do: [manifest()]
  defp manifest, do: Path.join(Mix.Project.manifest_path(), @manifest)

  @doc """
  Cleans up compilation artifacts.
  """
  def clean do
    Mix.Compilers.Erlang.clean(manifest())
  end

  ## Internal helpers

  defp scan_sources(files, include_path, source_paths) do
    include_paths = [include_path | source_paths]
    Enum.reduce(files, [], &scan_source(&2, &1, include_paths)) |> Enum.reverse()
  end

  defp scan_source(acc, file, include_paths) do
    erl_file = %{
      file: file,
      module: module_from_artifact(file),
      behaviours: [],
      compile: [],
      includes: [],
      invalid: false
    }

    case :epp.parse_file(to_erl_file(file), include_paths, []) do
      {:ok, forms} ->
        [List.foldl(tl(forms), erl_file, &do_form(file, &1, &2)) | acc]

      {:error, _error} ->
        acc
    end
  end

  defp do_form(file, form, erl) do
    case form do
      {:attribute, _, :file, {include_file, _}} when file != include_file ->
        if File.regular?(include_file) do
          %{erl | includes: [include_file | erl.includes]}
        else
          erl
        end

      {:attribute, _, :behaviour, behaviour} ->
        %{erl | behaviours: [behaviour | erl.behaviours]}

      {:attribute, _, :compile, value} when is_list(value) ->
        %{erl | compile: value ++ erl.compile}

      {:attribute, _, :compile, value} ->
        %{erl | compile: [value | erl.compile]}

      _ ->
        erl
    end
  end

  defp sort_dependencies(erls) do
    graph = :digraph.new()

    _ =
      for erl <- erls do
        :digraph.add_vertex(graph, erl.module, erl)
      end

    _ =
      for erl <- erls do
        _ = for b <- erl.behaviours, do: :digraph.add_edge(graph, b, erl.module)

        _ =
          for c <- erl.compile do
            case c do
              {:parse_transform, transform} -> :digraph.add_edge(graph, transform, erl.module)
              _ -> :ok
            end
          end

        :ok
      end

    result =
      case :digraph_utils.topsort(graph) do
        false ->
          erls

        mods ->
          for m <- mods, do: elem(:digraph.vertex(graph, m), 1)
      end

    :digraph.delete(graph)
    result
  end

  defp annotate_target(erl, compile_path, force) do
    beam = Path.join(compile_path, "#{erl.module}.beam")

    if force || Mix.Utils.stale?([erl.file | erl.includes], [beam]) do
      {:stale, erl.file, beam}
    else
      {:ok, erl.file, beam}
    end
  end

  defp module_from_artifact(artifact) do
    artifact |> Path.basename() |> Path.rootname() |> String.to_atom()
  end
end

defmodule Mix.Tasks.Compile.Erlang do
  use Mix.Task.Compiler
  import Mix.Compilers.Erlang

  @recursive true
  @manifest ".compile.erlang"
  @switches [force: :boolean, all_warnings: :boolean]

  @moduledoc """
  Compiles Erlang source files.

  When this task runs, it will first check the modification times of
  all files to be compiled and if they haven't been
  changed since the last compilation, it will not compile
  them. If any of them have changed, it compiles
  everything.

  For this reason, the task touches your `:compile_path`
  directory and sets the modification time to the current
  time and date at the end of each compilation. You can
  force compilation regardless of modification times by passing
  the `--force` option.

  ## Command line options

    * `--force` - forces compilation regardless of modification times

    * `--all-warnings` - prints warnings even from files that do not need to be
      recompiled

  ## Configuration

    * `ERL_COMPILER_OPTIONS` - can be used to give default compile options.
      The value must be a valid Erlang term. If the value is a list, it will
      be used as is. If it is not a list, it will be put into a list.

    * `:erlc_paths` - directories to find source files.
      Defaults to `["src"]`.

    * `:erlc_include_path` - directory for adding include files.
      Defaults to `"include"`.

    * `:erlc_options` - compilation options that apply to Erlang's
      compiler. Defaults to `[:debug_info]`.

      For a complete list of options, see `:compile.file/2`.

  For example, to configure the `erlc_options` for your Erlang project you
  may run:

      erlc_options: [:debug_info, {:i, 'path/to/include'}]

  """

  @doc """
  Runs this task.
  """
  def run(args) do
    {opts, _, _} = OptionParser.parse(args, switches: @switches)
    project      = Mix.Project.config
    source_paths = project[:erlc_paths]
    Mix.Compilers.Erlang.assert_valid_erlc_paths(source_paths)
    files = Mix.Utils.extract_files(source_paths, [:erl])
    do_run(files, opts, project, source_paths)
  end

  defp do_run([], _, _, _), do: :noop
  defp do_run(files, opts, project, source_paths) do
    include_path = to_erl_file project[:erlc_include_path]
    compile_path = to_erl_file Mix.Project.compile_path(project)

    erlc_options = project[:erlc_options] || []
    unless is_list(erlc_options) do
      Mix.raise ":erlc_options should be a list of options, got: #{inspect(erlc_options)}"
    end
    erlc_options = erlc_options ++ [:return, :report, outdir: compile_path, i: include_path]
    erlc_options = Enum.map erlc_options, fn
      {kind, dir} when kind in [:i, :outdir] ->
        {kind, to_erl_file(dir)}
      opt ->
        opt
    end

    compile_path = Path.relative_to(compile_path, File.cwd!)

    tuples = files
             |> scan_sources(include_path, source_paths)
             |> sort_dependencies
             |> Enum.map(&annotate_target(&1, compile_path, opts[:force]))

    Mix.Compilers.Erlang.compile(manifest(), tuples, opts, fn
      input, _output ->
        # We're purging the module because a previous compiler (e.g. Phoenix)
        # might have already loaded the previous version of it.
        module = Path.basename(input, ".erl") |> String.to_atom
        :code.purge(module)
        :code.delete(module)

        file = to_erl_file(Path.rootname(input, ".erl"))
        case :compile.file(file, erlc_options) do
          {:error, :badarg} ->
            Mix.raise "Compiling Erlang #{inspect file} failed with ArgumentError, probably because of invalid :erlc_options"
          result ->
            result
        end
    end)
  end

  @doc """
  Returns Erlang manifests.
  """
  def manifests, do: [manifest()]
  defp manifest, do: Path.join(Mix.Project.manifest_path, @manifest)

  @doc """
  Cleans up compilation artifacts.
  """
  def clean do
    Mix.Compilers.Erlang.clean(manifest())
  end

  ## Internal helpers

  defp scan_sources(files, include_path, source_paths) do
    include_paths = [include_path | source_paths]
    Enum.reduce(files, [], &scan_source(&2, &1, include_paths)) |> Enum.reverse
  end

  defp scan_source(acc, file, include_paths) do
    erl_file = %{file: file, module: module_from_artifact(file),
                 behaviours: [], compile: [], includes: [], invalid: false}
    case :epp.parse_file(to_erl_file(file), include_paths, []) do
      {:ok, forms} ->
        [List.foldl(tl(forms), erl_file, &do_form(file, &1, &2)) | acc]
      {:error, _error} ->
        acc
    end
  end

  defp do_form(file, form, erl) do
    case form do
      {:attribute, _, :file, {include_file, _}} when file != include_file ->
        if File.regular?(include_file) do
          %{erl | includes: [include_file | erl.includes]}
        else
          erl
        end
      {:attribute, _, :behaviour, behaviour} ->
        %{erl | behaviours: [behaviour | erl.behaviours]}
      {:attribute, _, :compile, value} when is_list(value) ->
        %{erl | compile: value ++ erl.compile}
      {:attribute, _, :compile, value} ->
        %{erl | compile: [value | erl.compile]}
      _ ->
        erl
    end
  end

  defp sort_dependencies(erls) do
    graph = :digraph.new

    _ = for erl <- erls do
      :digraph.add_vertex(graph, erl.module, erl)
    end

    _ = for erl <- erls do
      _ = for b <- erl.behaviours, do: :digraph.add_edge(graph, b, erl.module)
      _ = for c <- erl.compile do
        case c do
          {:parse_transform, transform} -> :digraph.add_edge(graph, transform, erl.module)
          _ -> :ok
        end
      end
      :ok
    end

    result =
      case :digraph_utils.topsort(graph) do
        false -> erls
        mods  ->
          for m <- mods, do: elem(:digraph.vertex(graph, m), 1)
      end

    :digraph.delete(graph)
    result
  end

  defp annotate_target(erl, compile_path, force) do
    beam = Path.join(compile_path, "#{erl.module}.beam")

    if force || Mix.Utils.stale?([erl.file | erl.includes], [beam]) do
      {:stale, erl.file, beam}
    else
      {:ok, erl.file, beam}
    end
  end

  defp module_from_artifact(artifact) do
    artifact |> Path.basename |> Path.rootname |> String.to_atom
  end
end


Fix router bug

Fix router bug

defmodule ApiWeb.Router do
  use Api.Web, :router
  use Plug.ErrorHandler
  use Sentry.Plug

  pipeline :api do
    plug :accepts, ["json"]
    plug Guardian.Plug.VerifyHeader, realm: "Bearer"
    plug Guardian.Plug.LoadResource
  end

  if Mix.env == :dev do
    forward "/sent_emails", Bamboo.EmailPreviewPlug
  end

  scope "/api", ApiWeb do
    pipe_through :api

    resources "/projects", ProjectController, except: [:new, :edit]
    resources "/teams", TeamController, except: [:new, :edit]
    resources "/users", UserController, except: [:new, :edit]
    resources "/invites", InviteController, except: [:new, :edit, :update]
    resources "/workshops", WorkshopController, only: [:index, :show]
    resources "/blogposts", BlogPostController, only: [:index, :show]

    get "/me", SessionController, :me
    post "/login", SessionController, :create
    delete "/logout", SessionController, :delete

    post "/invites/invite_to_slack", InviteController, :invite_to_slack
    put "/invites/:id/accept", InviteController, :accept

    delete "/teams/:id/remove/:user_id", TeamController, :remove

    post "/workshops/:id/join", WorkshopController, :join
    delete "/workshops/:id/leave", WorkshopController, :leave

    get "/voting/info_begin", VotingController, :info_begin
    get "/voting/info_end", VotingController, :info_end
    get "/voting/vote", VotingController, :get_votes
    post "/voting/vote", VotingController, :upsert_votes

    get "/users/password/get_token", UserController, :get_token
    post "/users/password/recover", UserController, :recover_password

    scope "/admin", as: :admin do
      resources "/users", Admin.UserController, except: [:new, :edit, :create]
      resources "/workshops", Admin.WorkshopController, except: [:new, :edit]
      resources "/teams", Admin.TeamController, except: [:new, :edit, :create]
      resources "/blogposts", Admin.BlogPostController, only: [:create, :update, :delete]

      get "/stats", Admin.StatsController, :stats

      post "/teams/:id/disqualify", Admin.TeamController, :disqualify
      post "/teams/:id/repo", Admin.TeamController, :create_repo
      post "/teams/:id/repo/add_users", Admin.TeamController, :add_users_to_repo
      delete "/teams/:id/remove/:user_id", Admin.TeamController, :remove

      post "/invites/sync", Admin.InviteController, :sync

      post "/checkin/:id", Admin.UserController, :checkin
      delete "/checkin/:id", Admin.UserController, :remove_checkin

      post "/workshops/:id/checkin/:user_id", Admin.WorkshopController, :checkin
      delete "/workshops/:id/checkin/:user_id", Admin.WorkshopController, :remove_checkin

      get "/competition/status", Admin.CompetitionController, :status
      post "/competition/start_voting", Admin.CompetitionController, :start_voting
      post "/competition/end_voting", Admin.CompetitionController, :end_voting

      get "/paper_vote/:id", Admin.PaperVoteController, :show
      post "/paper_vote", Admin.PaperVoteController, :create
      post "/paper_vote/:id/redeem", Admin.PaperVoteController, :redeem
      post "/paper_vote/:id/annul", Admin.PaperVoteController, :annul
    end
  end
end

defmodule ApiWeb.Router do
  use Api.Web, :router
  use Plug.ErrorHandler
  use Sentry.Plug

  pipeline :api do
    plug :accepts, ["json"]
    plug Guardian.Plug.VerifyHeader, realm: "Bearer"
    plug Guardian.Plug.LoadResource
  end

  if Mix.env == :dev do
    forward "/sent_emails", Bamboo.EmailPreviewPlug
  end

  scope "/api", ApiWeb do
    pipe_through :api

    resources "/projects", ProjectController, except: [:new, :edit]
    resources "/teams", TeamController, except: [:new, :edit]
    resources "/users", UserController, except: [:new, :edit]
    resources "/invites", InviteController, except: [:new, :edit, :update]
    resources "/workshops", WorkshopController, only: [:index, :show]
    resources "/blogposts", BlogpostController, only: [:index, :show]

    get "/me", SessionController, :me
    post "/login", SessionController, :create
    delete "/logout", SessionController, :delete

    post "/invites/invite_to_slack", InviteController, :invite_to_slack
    put "/invites/:id/accept", InviteController, :accept

    delete "/teams/:id/remove/:user_id", TeamController, :remove

    post "/workshops/:id/join", WorkshopController, :join
    delete "/workshops/:id/leave", WorkshopController, :leave

    get "/voting/info_begin", VotingController, :info_begin
    get "/voting/info_end", VotingController, :info_end
    get "/voting/vote", VotingController, :get_votes
    post "/voting/vote", VotingController, :upsert_votes

    get "/users/password/get_token", UserController, :get_token
    post "/users/password/recover", UserController, :recover_password

    scope "/admin", as: :admin do
      resources "/users", Admin.UserController, except: [:new, :edit, :create]
      resources "/workshops", Admin.WorkshopController, except: [:new, :edit]
      resources "/teams", Admin.TeamController, except: [:new, :edit, :create]
      resources "/blogposts", Admin.BlogpostController, only: [:create, :update, :delete]

      get "/stats", Admin.StatsController, :stats

      post "/teams/:id/disqualify", Admin.TeamController, :disqualify
      post "/teams/:id/repo", Admin.TeamController, :create_repo
      post "/teams/:id/repo/add_users", Admin.TeamController, :add_users_to_repo
      delete "/teams/:id/remove/:user_id", Admin.TeamController, :remove

      post "/invites/sync", Admin.InviteController, :sync

      post "/checkin/:id", Admin.UserController, :checkin
      delete "/checkin/:id", Admin.UserController, :remove_checkin

      post "/workshops/:id/checkin/:user_id", Admin.WorkshopController, :checkin
      delete "/workshops/:id/checkin/:user_id", Admin.WorkshopController, :remove_checkin

      get "/competition/status", Admin.CompetitionController, :status
      post "/competition/start_voting", Admin.CompetitionController, :start_voting
      post "/competition/end_voting", Admin.CompetitionController, :end_voting

      get "/paper_vote/:id", Admin.PaperVoteController, :show
      post "/paper_vote", Admin.PaperVoteController, :create
      post "/paper_vote/:id/redeem", Admin.PaperVoteController, :redeem
      post "/paper_vote/:id/annul", Admin.PaperVoteController, :annul
    end
  end
end


Add missing configuration in webapp mix.exs


add category param

add category param

defmodule Glasnost.Schema do
  use Absinthe.Schema
  import_types Glasnost.Schema.Types

  query do
    field :comments, list_of(:comment) do
      arg :blockchain, non_null(:string)
      arg :author, :string
      arg :tag, :string
      arg :category, :string
      arg :is_post, :boolean
      resolve &Glasnost.CommentResolver.all/2
    end

    field :comment, type: :comment do
      arg :blockchain, non_null(:string)
      arg :author, non_null(:string)
      arg :permlink, non_null(:string)
      resolve &Glasnost.CommentResolver.find/2
    end

    field :statistics, type: :statistic do
      resolve &Glasnost.StatisticsResolver.all/2
    end

  end

end

defmodule Glasnost.Schema do
  use Absinthe.Schema
  import_types Glasnost.Schema.Types

  query do
    field :comments, list_of(:comment) do
      arg :blockchain, non_null(:string)
      arg :author, :string
      arg :tag, :string
      arg :is_post, :boolean
      resolve &Glasnost.CommentResolver.all/2
    end

    field :comment, type: :comment do
      arg :blockchain, non_null(:string)
      arg :author, non_null(:string)
      arg :permlink, non_null(:string)
      resolve &Glasnost.CommentResolver.find/2
    end

    field :statistics, type: :statistic do
      resolve &Glasnost.StatisticsResolver.all/2
    end

  end

end


Bank-account: test that amount is added to balance

As of now, the tests for bank-account pass, even if the implementation replaces balance, rather than adding amount to the existing balance.

if System.get_env("EXERCISM_TEST_EXAMPLES") do
  Code.load_file("example.exs")
else
  Code.load_file("account.exs")
end

ExUnit.start
ExUnit.configure exclude: :pending, trace: true

# The BankAccount module should support four calls:
#
# open_bank()
#   Called at the start of each test. Returns an account handle.
#
# close_bank(account)
#   Called at the end of each test.
#
# balance(account)
#   Get the balance of the bank account.
#
# update(account, amount)
#   Increment the balance of the bank account by the given amount.
#   The amount may be negative for a withdrawal.
#
# The initial value of the bank account should be 0.

defmodule BankAccountTest do
  use ExUnit.Case

  setup do
    account = BankAccount.open_bank
    { :ok, account: account }
  end

  # @tag :pending
  test "initial balance is 0", %{account: account} do
    assert BankAccount.balance(account) == 0
  end

  @tag :pending
  test "incrementing and checking balance", %{account: account} do
    assert BankAccount.balance(account) == 0
    BankAccount.update(account, 10)
    assert BankAccount.balance(account) == 10
  end

  @tag :pending
  test "amount is added to balance", %{account: account} do
    assert BankAccount.balance(account) == 0
    BankAccount.update(account, 10)
    BankAccount.update(account, 10)
    assert BankAccount.balance(account) == 20
  end

  @tag :pending
  test "incrementing balance from another process then checking it from test process", %{account: account} do
    assert BankAccount.balance(account) == 0
    this = self()
    spawn(fn ->
      BankAccount.update(account, 20)
      send(this, :continue)
    end)
    receive do
      :continue -> :ok
    after
      1000 -> flunk("Timeout")
    end
    assert BankAccount.balance(account) == 20
  end
end

if System.get_env("EXERCISM_TEST_EXAMPLES") do
  Code.load_file("example.exs")
else
  Code.load_file("account.exs")
end

ExUnit.start
ExUnit.configure exclude: :pending, trace: true

# The BankAccount module should support four calls:
#
# open_bank()
#   Called at the start of each test. Returns an account handle.
#
# close_bank(account)
#   Called at the end of each test.
#
# balance(account)
#   Get the balance of the bank account.
#
# update(account, amount)
#   Increment the balance of the bank account by the given amount.
#   The amount may be negative for a withdrawal.
#
# The initial value of the bank account should be 0.

defmodule BankAccountTest do
  use ExUnit.Case

  setup do
    account = BankAccount.open_bank
    { :ok, account: account }
  end

  # @tag :pending
  test "initial balance is 0", %{account: account} do
    assert BankAccount.balance(account) == 0
  end

  @tag :pending
  test "incrementing and checking balance", %{account: account} do
    assert BankAccount.balance(account) == 0
    BankAccount.update(account, 10)
    assert BankAccount.balance(account) == 10
  end

  @tag :pending
  test "incrementing balance from another process then checking it from test process", %{account: account} do
    assert BankAccount.balance(account) == 0
    this = self()
    spawn(fn ->
      BankAccount.update(account, 20)
      send(this, :continue)
    end)
    receive do
      :continue -> :ok
    after
      1000 -> flunk("Timeout")
    end
    assert BankAccount.balance(account) == 20
  end
end


Improve base adapter documentation

Improve base adapter documentation

defmodule Geolix.Adapter do
  @moduledoc """
  Adapter behaviour module.
  """

  @optional_callbacks [
    database_workers: 0,
    load_database: 1,
    unload_database: 1
  ]

  @doc """
  Returns the children to be supervised by `Geolix.Database.Supervisor`.

  If no automatic supervision should take place or it is intended to use a
  adapter specific supervisor (e.g. using the application config) this callback
  should be either unimplemented or return an empty list.
  """
  @callback database_workers() :: list

  @doc """
  Loads a given database into Geolix.

  Requires at least the fields `:id` and `:adapter`. Any other required
  fields depend on the adapter's requirements.
  """
  @callback load_database(database :: map) :: :ok | {:error, term}

  @doc """
  Looks up IP information.

  The adapter should expect the `opts` argument to contain a
  key `:where` defining the specific database to use for the lookup.
  """
  @callback lookup(ip :: :inet.ip_address(), opts :: Keyword.t()) :: map | nil

  @doc """
  Unloads a given database from Geolix.
  """
  @callback unload_database(database :: map) :: :ok
end

defmodule Geolix.Adapter do
  @moduledoc """
  Adapter behaviour module.
  """

  @optional_callbacks [
    database_workers: 0,
    load_database: 1,
    unload_database: 1
  ]

  @doc """
  Returns the children to be supervised by `Geolix.Database.Supervisor`.

  If no automatic supervision should take place or it is intended to use a
  adapter specific supervisor (e.g. using the application config) this callback
  should be either unimplemented or return an empty list.
  """
  @callback database_workers() :: list

  @doc """
  Loads a given database into Geolix.

  Requires at least the fields `:id` and `:adapter`. Any other required
  fields depend on the adapter's requirements.
  """
  @callback load_database(map) :: :ok | {:error, term}

  @doc """
  Unloads a given database from Geolix.

  Receives the configuration used when initially loading the database.
  """
  @callback unload_database(map) :: :ok

  @doc """
  Looks up IP information.

  The adapter should expect the `opts` argument to contain a
  key `:where` defining the specific database to use for the lookup.
  """
  @callback lookup(ip :: :inet.ip_address(), opts :: Keyword.t()) :: map | nil
end


Small fix

Small fix

defmodule Mail do
  alias Mail.Templates
  require Logger
  import Ecto.Query
  require Ecto.Query
  alias Plug.Conn

  @basename Application.get_env(:mailer, :basename)
  @hashid Hashids.new(salt: Application.get_env(:mailer, :hashid_salt))
  @wk2 File.read!("data/templates/wk2.txt")

  def send_group_email(design_id, sender, sender_nick, subject, content, from_web \\ true) do
    Survey.Email.insert(design_id, sender, subject, content, from_web)

    from = "#{group_address(design_id)}-design_group@mooc.encorelab.org"
    emails = Survey.DesignGroup.get_emails(design_id)
    if !from_web do
      sent = User.get_email(sender)
      emails = Enum.reject(emails, fn x -> x == sent end)
    end

    emails
    |> Enum.each(fn [id, email] ->
      html = Templates.group_email(content, sender_nick, id, @basename)
      %Mailman.Email{from: from, to: ["shaklev@gmail.com"],
        subject: subject, html: html}
      |> schedule_send
    end)
  end

  def schedule_send(email) do
    Survey.Job.add({Survey.Mailer, :deliver, [email]})
  end

  def group_address(id) do
    hash = Hashids.encode(@hashid, id)
  end

  def send_design do
    (from f in Survey.User,
    where: not is_nil(f.design_group_id))
    |> Survey.Repo.all
    |> Enum.map(&send_design1/1)
  end

  def send_design1(user) do
    if !Survey.User.is_unsubscribed?(user.id, "collab") do
      html = Templates.collab_wk2(user.id, @basename)
      email = %Mailman.Email{from: "noreply@mooc.encorelab.org", to: [user.edx_email],
        subject: "Design Strand week 2 is open!", html: html}
      Survey.Job.add({Survey.Mailer, :deliver, [email]})
    end
  end

  def send_notification(room, entered, design, userids) when is_list(userids) do
    if Application.get_env(:mailer, :disabled) do
      Logger.warn("Emailing disabled")
    else
      Task.Supervisor.start_child(:email_sup, fn ->
        Enum.each(userids, fn [id, nick] ->
          if !Survey.User.is_unsubscribed?(id, "collab") &&
            Survey.ChatPresence.not_online?(room, id) do
              generate_notification(entered, design, [id, nick])
              |> Survey.Mailer.deliver

              Logger.info("Sent email to #{id}")
          end
        end)
      end)
    end
  end

  def generate_notification(entered, design, [id, nick]) do
    email = (from f in Survey.User,
    where: f.id == ^id,
    select: f.edx_email) |> Survey.Repo.one

    text = Templates.collab_notification_text(nick, entered, design,
      id, @basename)
    html = Templates.collab_notification_html(nick, entered, design,
      id, @basename)

    %Mailman.Email{
      subject: "#{entered} entered the collaborative workbench",
      from: "noreply@mooc.encorelab.org",
      to: [email],
      text: text,
      html: html }
  end

  def send_wk2 do
    template = %Mailman.Email{
      subject: "Welcome to Week 2",
      from: "noreply@mooc.encorelab.org",
    }

    # Enum.map((from f in Survey.User, where: f.id == 647) |> Survey.Repo.all, fn x ->

    Enum.map((from Survey.User) |> Survey.Repo.all, fn x ->
      Task.Supervisor.start_child(:email_sup, fn ->
        :timer.sleep(:random.uniform(100000))
        send_wk2_individ(template, x)
      end)
    end)
  end

  def send_wk2_individ(template, user) do
    {subj, text} = Mail.Contents.get_data(user)
    |> Mail.Contents.generate
    html = Templates.common(subj, text)

    %{ template |
      to: [user.edx_email],
      html: html,
      text: text}
    |> Survey.Mailer.deliver

    Logger.info("Sending wk2 mail to #{user.id}")
  end

  def gen_url(id, url) do
    term = %{url: url, userid: id}
    id = Survey.Cache.store(term)
    hash = Hashids.encode(@hashid, id)
    @basename <> "/email/" <> hash
  end
end

defmodule Mail do
  alias Mail.Templates
  require Logger
  import Ecto.Query
  require Ecto.Query
  alias Plug.Conn

  @basename Application.get_env(:mailer, :basename)
  @hashid Hashids.new(salt: Application.get_env(:mailer, :hashid_salt))
  @wk2 File.read!("data/templates/wk2.txt")

  def send_group_email(design_id, sender, sender_nick, subject, content, from_web) do
    Survey.Email.insert(design_id, sender, subject, content, from_web)

    from = "#{group_address(design_id)}-design_group@mooc.encorelab.org"
    emails = Survey.DesignGroup.get_emails(design_id)
    if !from_web do
      sent = User.get_email(sender)
      emails = Enum.reject(emails, fn x -> x == sent end)
    end

    emails
    |> Enum.each(fn [id, email] ->
      html = Templates.group_email(content, sender_nick, id, @basename)
      %Mailman.Email{from: from, to: ["shaklev@gmail.com"],
        subject: subject, html: html}
      |> schedule_send
    end)
  end

  def schedule_send(email) do
    Survey.Job.add({Survey.Mailer, :deliver, [email]})
  end

  def group_address(id) do
    hash = Hashids.encode(@hashid, id)
  end

  def send_design do
    (from f in Survey.User,
    where: not is_nil(f.design_group_id))
    |> Survey.Repo.all
    |> Enum.map(&send_design1/1)
  end

  def send_design1(user) do
    if !Survey.User.is_unsubscribed?(user.id, "collab") do
      html = Templates.collab_wk2(user.id, @basename)
      email = %Mailman.Email{from: "noreply@mooc.encorelab.org", to: [user.edx_email],
        subject: "Design Strand week 2 is open!", html: html}
      Survey.Job.add({Survey.Mailer, :deliver, [email]})
    end
  end

  def send_notification(room, entered, design, userids) when is_list(userids) do
    if Application.get_env(:mailer, :disabled) do
      Logger.warn("Emailing disabled")
    else
      Task.Supervisor.start_child(:email_sup, fn ->
        Enum.each(userids, fn [id, nick] ->
          if !Survey.User.is_unsubscribed?(id, "collab") &&
            Survey.ChatPresence.not_online?(room, id) do
              generate_notification(entered, design, [id, nick])
              |> Survey.Mailer.deliver

              Logger.info("Sent email to #{id}")
          end
        end)
      end)
    end
  end

  def generate_notification(entered, design, [id, nick]) do
    email = (from f in Survey.User,
    where: f.id == ^id,
    select: f.edx_email) |> Survey.Repo.one

    text = Templates.collab_notification_text(nick, entered, design,
      id, @basename)
    html = Templates.collab_notification_html(nick, entered, design,
      id, @basename)

    %Mailman.Email{
      subject: "#{entered} entered the collaborative workbench",
      from: "noreply@mooc.encorelab.org",
      to: [email],
      text: text,
      html: html }
  end

  def send_wk2 do
    template = %Mailman.Email{
      subject: "Welcome to Week 2",
      from: "noreply@mooc.encorelab.org",
    }

    # Enum.map((from f in Survey.User, where: f.id == 647) |> Survey.Repo.all, fn x ->

    Enum.map((from Survey.User) |> Survey.Repo.all, fn x ->
      Task.Supervisor.start_child(:email_sup, fn ->
        :timer.sleep(:random.uniform(100000))
        send_wk2_individ(template, x)
      end)
    end)
  end

  def send_wk2_individ(template, user) do
    {subj, text} = Mail.Contents.get_data(user)
    |> Mail.Contents.generate
    html = Templates.common(subj, text)

    %{ template |
      to: [user.edx_email],
      html: html,
      text: text}
    |> Survey.Mailer.deliver

    Logger.info("Sending wk2 mail to #{user.id}")
  end

  def gen_url(id, url) do
    term = %{url: url, userid: id}
    id = Survey.Cache.store(term)
    hash = Hashids.encode(@hashid, id)
    @basename <> "/email/" <> hash
  end
end


Provide default options for Recs.start_link/1

defmodule Recs do
  @type command :: [binary]

  @default_opts [
    host: "localhost",
    port: 6379,
  ]

  @redis_opts ~w(host port password database)a

  @spec start_link(Keyword.t) :: GenServer.on_start
  def start_link(opts \\ []) do
    {_redis_opts, connection_opts} = Keyword.split(opts, @redis_opts)
    opts = Keyword.merge(@default_opts, opts)
    Connection.start_link(Recs.Connection, opts, connection_opts)
  end

  @spec command(pid, command) :: Recs.Protocol.redis_value
  def command(conn, args) do
    Connection.call(conn, {:command, args})
  end

  @spec pipeline(pid, [command]) :: [Recs.Protocol.redis_value]
  def pipeline(conn, commands) do
    Connection.call(conn, {:pipeline, commands})
  end
end

defmodule Recs do
  @type command :: [binary]

  @redis_opts ~w(host port password database)a

  @spec start_link(Keyword.t) :: GenServer.on_start
  def start_link(opts) do
    {_redis_opts, connection_opts} = Keyword.split(opts, @redis_opts)
    Connection.start_link(Recs.Connection, opts, connection_opts)
  end

  @spec command(pid, command) :: Recs.Protocol.redis_value
  def command(conn, args) do
    Connection.call(conn, {:command, args})
  end

  @spec pipeline(pid, [command]) :: [Recs.Protocol.redis_value]
  def pipeline(conn, commands) do
    Connection.call(conn, {:pipeline, commands})
  end
end


Remove generators

Remove generators

# 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 :web_hello_world, WebHelloWorld.Endpoint,
  url: [host: "localhost"],
  root: Path.dirname(__DIR__),
  render_errors: [accepts: ~w(json)]

# 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