From 287bf6716f68e3618bae03b08c44886696fb3931 Mon Sep 17 00:00:00 2001 From: Cam Cook Date: Tue, 27 May 2025 21:23:51 -0400 Subject: [PATCH 1/4] feat: get hex user --- lib/aura/model/hex_user.ex | 19 +++++++++++++++ lib/aura/packages.ex | 24 +++++++++++-------- lib/aura/users.ex | 12 ++++++++++ test/aura/model/hex_user_test.exs | 16 +++++++++++++ test/aura/users_test.exs | 28 ++++++++++++++++++++++ test/support/factories/hex_user_factory.ex | 21 ++++++++++++++++ test/support/factory.ex | 1 + 7 files changed, 111 insertions(+), 10 deletions(-) create mode 100644 lib/aura/model/hex_user.ex create mode 100644 lib/aura/users.ex create mode 100644 test/aura/model/hex_user_test.exs create mode 100644 test/aura/users_test.exs create mode 100644 test/support/factories/hex_user_factory.ex 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..437da0e --- /dev/null +++ b/lib/aura/users.ex @@ -0,0 +1,12 @@ +defmodule Aura.Users do + @moduledoc false + + alias Aura.Model.HexUser + alias Aura.Requester + + def get_user(username_or_email, opts \\ []) do + with {:ok, %{body: body}} <- Requester.get("/users/#{username_or_email}", opts) do + {:ok, HexUser.build(body)} + end + end +end 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..5b349c2 --- /dev/null +++ b/test/aura/users_test.exs @@ -0,0 +1,28 @@ +defmodule Aura.UsersTest do + use ExUnit.Case + + 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 +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 From 8f5c22e818522bf8066ea98896913c629e75bb65 Mon Sep 17 00:00:00 2001 From: Cam Cook Date: Tue, 27 May 2025 21:53:30 -0400 Subject: [PATCH 2/4] feat: create hex user --- lib/aura/users.ex | 28 ++++++++++++++++++++++++++++ lib/aura/util/requester.ex | 2 +- test/aura/users_test.exs | 13 +++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/aura/users.ex b/lib/aura/users.ex index 437da0e..408eb5d 100644 --- a/lib/aura/users.ex +++ b/lib/aura/users.ex @@ -4,6 +4,34 @@ defmodule Aura.Users do 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 \\ []) do with {:ok, %{body: body}} <- Requester.get("/users/#{username_or_email}", opts) do {:ok, HexUser.build(body)} 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/users_test.exs b/test/aura/users_test.exs index 5b349c2..f257b09 100644 --- a/test/aura/users_test.exs +++ b/test/aura/users_test.exs @@ -25,4 +25,17 @@ defmodule Aura.UsersTest do 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 end From 1b0f8359986ef3a0efa17c650132135bd2227f56 Mon Sep 17 00:00:00 2001 From: Cam Cook Date: Tue, 27 May 2025 22:06:03 -0400 Subject: [PATCH 3/4] feat: get current user --- lib/aura/users.ex | 6 ++++++ test/aura/users_test.exs | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/lib/aura/users.ex b/lib/aura/users.ex index 408eb5d..8159f49 100644 --- a/lib/aura/users.ex +++ b/lib/aura/users.ex @@ -37,4 +37,10 @@ defmodule Aura.Users 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 end diff --git a/test/aura/users_test.exs b/test/aura/users_test.exs index f257b09..a7e5b8f 100644 --- a/test/aura/users_test.exs +++ b/test/aura/users_test.exs @@ -38,4 +38,16 @@ defmodule Aura.UsersTest do 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, %Aura.Model.HexUser{}} = Users.get_current_user() + + Application.delete_env(:aura, :repo_url) + Application.delete_env(:aura, :api_key) + end end From e48cd2a3b05f98c7cb3bc5db1e60328aaea8dce7 Mon Sep 17 00:00:00 2001 From: Cam Cook Date: Tue, 27 May 2025 22:08:49 -0400 Subject: [PATCH 4/4] feat: reset user password --- lib/aura/users.ex | 8 +++++++- test/aura/users_test.exs | 15 ++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/aura/users.ex b/lib/aura/users.ex index 8159f49..e76118b 100644 --- a/lib/aura/users.ex +++ b/lib/aura/users.ex @@ -32,7 +32,7 @@ defmodule Aura.Users do end end - def get_user(username_or_email, opts \\ []) do + 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 @@ -43,4 +43,10 @@ defmodule Aura.Users 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/test/aura/users_test.exs b/test/aura/users_test.exs index a7e5b8f..e1dc776 100644 --- a/test/aura/users_test.exs +++ b/test/aura/users_test.exs @@ -1,6 +1,7 @@ defmodule Aura.UsersTest do use ExUnit.Case + alias Aura.Model.HexUser alias Aura.Packages alias Aura.Users @@ -45,7 +46,19 @@ defmodule Aura.UsersTest do Application.put_env(:aura, :api_key, api_key) Application.put_env(:aura, :repo_url, mock_repo) - {:ok, %Aura.Model.HexUser{}} = Users.get_current_user() + {: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)