diff --git a/lib/aura/model/hex_user.ex b/lib/aura/model/hex_user.ex new file mode 100644 index 0000000..3f11bb4 --- /dev/null +++ b/lib/aura/model/hex_user.ex @@ -0,0 +1,19 @@ +defmodule Aura.Model.HexUser do + @moduledoc false + + import Aura.Model.Common + + defstruct [ + :username, + :email, + :inserted_at, + :updated_at, + :url + ] + + def build(m) when is_map(m) do + m + |> prepare() + |> then(&struct(Aura.Model.HexUser, &1)) + end +end diff --git a/lib/aura/packages.ex b/lib/aura/packages.ex index 78e88fe..e6296eb 100644 --- a/lib/aura/packages.ex +++ b/lib/aura/packages.ex @@ -9,14 +9,14 @@ defmodule Aura.Packages do stream_paginate("/packages", opts) end - def list_package_owners(name) do - with {:ok, %{body: body}} <- Requester.get("/packages/#{name}/owners") do + def list_package_owners(name, opts \\ []) do + with {:ok, %{body: body}} <- Requester.get("/packages/#{name}/owners", opts) do {:ok, Enum.map(body, &HexPackageOwner.build/1)} end end - def get_package(name) do - with {:ok, %{body: body}} <- Requester.get("/packages/#{name}") do + def get_package(name, opts \\ []) do + with {:ok, %{body: body}} <- Requester.get("/packages/#{name}", opts) do {:ok, HexPackage.build(body)} end end @@ -25,21 +25,23 @@ defmodule Aura.Packages do qparams = Keyword.merge([page: 1], opts) + opts = Keyword.delete(opts, :page) + start_fun = fn -> max(1, qparams[:page]) end end_fun = fn _ -> :ok end - continue_fun = &paginate_with_page(&1, path, qparams) + continue_fun = &paginate_with_page(&1, path, qparams, opts) Stream.resource(start_fun, continue_fun, end_fun) end - defp paginate_with_page(page, _path, _qparams) when page < 1 do + defp paginate_with_page(page, _path, _qparams, _opts) when page < 1 do {:halt, page} end - defp paginate_with_page(page, path, qparams) when page >= 1 do + defp paginate_with_page(page, path, qparams, opts) when page >= 1 do path - |> get_package_page(page, qparams) + |> get_package_page(page, qparams, opts) |> case do {:ok, package_page} -> packages = Enum.map(package_page, &HexPackage.build/1) @@ -51,10 +53,12 @@ defmodule Aura.Packages do end end - defp get_package_page(path, page, qparams) do + defp get_package_page(path, page, qparams, opts) do qparams = Keyword.put(qparams, :page, page) - with {:ok, %{body: body}} <- Aura.Requester.get(path, qparams: qparams) do + opts = Keyword.put(opts, :qparams, qparams) + + with {:ok, %{body: body}} <- Aura.Requester.get(path, opts) do {:ok, body} end end diff --git a/lib/aura/users.ex b/lib/aura/users.ex new file mode 100644 index 0000000..e76118b --- /dev/null +++ b/lib/aura/users.ex @@ -0,0 +1,52 @@ +defmodule Aura.Users do + @moduledoc false + + alias Aura.Model.HexUser + alias Aura.Requester + + require Logger + + def create_user(username, password, email, opts \\ []) + when is_bitstring(username) and is_bitstring(password) and is_bitstring(email) do + if !opts[:repo_url] do + Logger.warning( + "By using create_user, you are agreeing to Hex's terms of service. See: https://hex.pm/policies/termsofservice" + ) + end + + opts = Keyword.merge([json: %{username: username, password: password, email: email}], opts) + + # See: https://github.com/hexpm/specifications/issues/41 + "/users" + |> Requester.post(opts) + |> case do + {:ok, %{body: body}} -> + try do + {:ok, HexUser.build(body)} + rescue + _ -> {:ok, :got_good_status} + end + + err -> + err + end + end + + def get_user(username_or_email, opts \\ []) when is_bitstring(username_or_email) do + with {:ok, %{body: body}} <- Requester.get("/users/#{username_or_email}", opts) do + {:ok, HexUser.build(body)} + end + end + + def get_current_user(opts \\ []) do + with {:ok, %{body: body}} <- Requester.get("/users/me", opts) do + {:ok, HexUser.build(body)} + end + end + + def reset_user_password(username_or_email, opts \\ []) do + with {:ok, _} <- Requester.post("/users/#{username_or_email}/reset", opts) do + :ok + end + end +end diff --git a/lib/aura/util/requester.ex b/lib/aura/util/requester.ex index 606ae59..e618da8 100644 --- a/lib/aura/util/requester.ex +++ b/lib/aura/util/requester.ex @@ -35,7 +35,7 @@ defmodule Aura.Requester do respect_limits(headers) resp - {:ok, %Req.Response{status: 204, body: _body, headers: headers}} = resp -> + {:ok, %Req.Response{status: status, body: _body, headers: headers}} = resp when status >= 200 and status < 300 -> respect_limits(headers) resp diff --git a/test/aura/model/hex_user_test.exs b/test/aura/model/hex_user_test.exs new file mode 100644 index 0000000..f6b78c3 --- /dev/null +++ b/test/aura/model/hex_user_test.exs @@ -0,0 +1,16 @@ +defmodule Aura.Model.HexUserTest do + use ExUnit.Case + + import Aura.Factory + + alias Aura.Model.HexUser + + @moduletag :capture_log + + doctest HexUser + + test "build_many" do + list = build_list(100, :hex_user) + assert Enum.count(list) == 100 + end +end diff --git a/test/aura/users_test.exs b/test/aura/users_test.exs new file mode 100644 index 0000000..e1dc776 --- /dev/null +++ b/test/aura/users_test.exs @@ -0,0 +1,66 @@ +defmodule Aura.UsersTest do + use ExUnit.Case + + alias Aura.Model.HexUser + alias Aura.Packages + alias Aura.Users + + doctest Users + @moduletag :capture_log + + test "get user" do + # use hex + packages = Enum.take(Packages.list_packages(), 5) + refute Enum.empty?(packages) + + Enum.each(packages, fn package -> + {:ok, owners} = Packages.list_package_owners(package.name) + refute Enum.empty?(owners) + + Enum.each(owners, fn owner -> + assert {:ok, user} = Users.get_user(owner.username) + assert user.username + assert user.inserted_at + assert user.updated_at + assert user.url + end) + end) + end + + test "create user" do + # use another repo + mock_repo = TestHelper.get_mock_repo() + Application.put_env(:aura, :repo_url, mock_repo) + username = Faker.Internet.user_name() + email = Faker.Internet.email() + password = Faker.Internet.slug() + + # See: https://github.com/hexpm/specifications/issues/41 + assert {:ok, _} = Users.create_user(username, email, password, raw: true) + Application.delete_env(:aura, :repo_url) + end + + test "get current user" do + mock_repo = TestHelper.get_mock_repo() + api_key = TestHelper.get_mock_api_key() + Application.put_env(:aura, :api_key, api_key) + Application.put_env(:aura, :repo_url, mock_repo) + + {:ok, %HexUser{}} = Users.get_current_user() + + Application.delete_env(:aura, :repo_url) + Application.delete_env(:aura, :api_key) + end + + test "reset user password" do + mock_repo = TestHelper.get_mock_repo() + api_key = TestHelper.get_mock_api_key() + Application.put_env(:aura, :api_key, api_key) + Application.put_env(:aura, :repo_url, mock_repo) + + assert :ok = Users.reset_user_password("ericmj") + + Application.delete_env(:aura, :repo_url) + Application.delete_env(:aura, :api_key) + end +end diff --git a/test/support/factories/hex_user_factory.ex b/test/support/factories/hex_user_factory.ex new file mode 100644 index 0000000..e0a6c8c --- /dev/null +++ b/test/support/factories/hex_user_factory.ex @@ -0,0 +1,21 @@ +defmodule Aura.Factory.HexUserFactory do + @moduledoc false + + defmacro __using__(_opts) do + quote do + def hex_user_factory(attrs) do + inserted_at = Faker.DateTime.backward(40) + + %Aura.Model.HexUser{ + username: Faker.Internet.user_name(), + email: Faker.Internet.email(), + url: Faker.Internet.url(), + inserted_at: inserted_at, + updated_at: Faker.DateTime.between(inserted_at, DateTime.now!("Etc/UTC")) + } + |> merge_attributes(attrs) + |> evaluate_lazy_attributes() + end + end + end +end diff --git a/test/support/factory.ex b/test/support/factory.ex index b8434ca..fae4078 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -6,4 +6,5 @@ defmodule Aura.Factory do use Aura.Factory.HexPackageOwnerFactory use Aura.Factory.HexReleaseFactory use Aura.Factory.HexAPIKeyFactory + use Aura.Factory.HexUserFactory end