From c68940611401fe5a896520ca595a6dc34166e5b5 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Tue, 5 Mar 2019 17:23:05 +0100 Subject: [PATCH] Extract User from Actors context Mobilizon.Actors.User -> Mobilizon.Users.User Also Mobilizon.Actors.Service now become Mobilizon.User.Service And Mobilizon.Users and Mobilizon.UsersTest is introduced. Signed-off-by: Thomas Citharel --- lib/mix/tasks/mobilizon/create_bot.ex | 5 +- lib/mobilizon/actors/actor.ex | 3 +- lib/mobilizon/actors/actors.ex | 257 +----------------- lib/mobilizon/actors/bot.ex | 3 +- lib/mobilizon/email/user.ex | 2 +- .../{actors => users}/service/activation.ex | 13 +- .../service/reset_password.ex | 9 +- .../{actors => users}/service/tools.ex | 4 +- lib/mobilizon/{actors => users}/user.ex | 5 +- lib/mobilizon/users/users.ex | 257 ++++++++++++++++++ .../controllers/node_info_controller.ex | 4 +- lib/mobilizon_web/guardian.ex | 6 +- lib/mobilizon_web/resolvers/comment.ex | 2 +- lib/mobilizon_web/resolvers/event.ex | 3 +- lib/mobilizon_web/resolvers/group.ex | 3 +- lib/mobilizon_web/resolvers/person.ex | 12 +- lib/mobilizon_web/resolvers/user.ex | 37 +-- test/mobilizon/actors/actors_test.exs | 105 +------ test/mobilizon/users/users_test.exs | 105 +++++++ .../resolvers/user_resolver_test.exs | 28 +- test/support/conn_case.ex | 2 +- test/support/factory.ex | 2 +- 22 files changed, 446 insertions(+), 421 deletions(-) rename lib/mobilizon/{actors => users}/service/activation.ex (78%) rename lib/mobilizon/{actors => users}/service/reset_password.ex (88%) rename lib/mobilizon/{actors => users}/service/tools.ex (90%) rename lib/mobilizon/{actors => users}/user.ex (97%) create mode 100644 lib/mobilizon/users/users.ex create mode 100644 test/mobilizon/users/users_test.exs diff --git a/lib/mix/tasks/mobilizon/create_bot.ex b/lib/mix/tasks/mobilizon/create_bot.ex index 510139aab..c4b85dd22 100644 --- a/lib/mix/tasks/mobilizon/create_bot.ex +++ b/lib/mix/tasks/mobilizon/create_bot.ex @@ -5,15 +5,16 @@ defmodule Mix.Tasks.Mobilizon.CreateBot do use Mix.Task alias Mobilizon.Actors + alias Mobilizon.Users alias Mobilizon.Actors.Bot - alias Mobilizon.Actors.User + alias Mobilizon.Users.User require Logger @shortdoc "Register user" def run([email, name, summary, type, url]) do Mix.Task.run("app.start") - with {:ok, %User{} = user} <- Actors.get_user_by_email(email, true), + with {:ok, %User{} = user} <- Users.get_user_by_email(email, true), actor <- Actors.register_bot_account(%{name: name, summary: summary}), {:ok, %Bot{} = bot} <- Actors.create_bot(%{ diff --git a/lib/mobilizon/actors/actor.ex b/lib/mobilizon/actors/actor.ex index 9cae6b29e..d7bbe8498 100644 --- a/lib/mobilizon/actors/actor.ex +++ b/lib/mobilizon/actors/actor.ex @@ -22,7 +22,8 @@ defmodule Mobilizon.Actors.Actor do import Ecto.Changeset alias Mobilizon.Actors - alias Mobilizon.Actors.{Actor, User, Follower, Member} + alias Mobilizon.Users.User + alias Mobilizon.Actors.{Actor, Follower, Member} alias Mobilizon.Events.Event import Ecto.Query diff --git a/lib/mobilizon/actors/actors.ex b/lib/mobilizon/actors/actors.ex index 62f9cc98a..e58a8e849 100644 --- a/lib/mobilizon/actors/actors.ex +++ b/lib/mobilizon/actors/actors.ex @@ -8,7 +8,8 @@ defmodule Mobilizon.Actors do alias Mobilizon.Repo - alias Mobilizon.Actors.{Actor, Bot, Member, Follower, User} + alias Mobilizon.Actors.{Actor, Bot, Member, Follower} + alias Mobilizon.Users.User alias Mobilizon.Service.ActivityPub # import Exgravatar @@ -56,33 +57,6 @@ defmodule Mobilizon.Actors do Repo.get!(Actor, id) end - @doc """ - Returns the associated actor for an user, either the default set one or the first found - """ - @spec get_actor_for_user(Mobilizon.Actors.User.t()) :: Mobilizon.Actors.Actor.t() - def get_actor_for_user(%Mobilizon.Actors.User{} = user) do - case Repo.one( - from(a in Actor, - join: u in User, - on: u.default_actor_id == a.id, - where: u.id == ^user.id - ) - ) do - nil -> - case user |> get_actors_for_user() do - [] -> nil - actors -> hd(actors) - end - - actor -> - actor - end - end - - def get_actors_for_user(%User{id: user_id}) do - Repo.all(from(a in Actor, where: a.user_id == ^user_id)) - end - @spec get_actor_with_everything(integer()) :: Ecto.Query defp do_get_actor_with_everything(id) do from(a in Actor, where: a.id == ^id, preload: [:organized_events, :followers, :followings]) @@ -145,13 +119,6 @@ defmodule Mobilizon.Actors do |> Repo.update() end - def update_user_default_actor(user_id, actor_id) do - with from(u in User, where: u.id == ^user_id, update: [set: [default_actor_id: ^actor_id]]) - |> Repo.update_all([]) do - Repo.get!(User, user_id) |> Repo.preload([:default_actor]) - end - end - @doc """ Deletes a Actor. @@ -255,34 +222,6 @@ defmodule Mobilizon.Actors do Repo.delete!(group) end - alias Mobilizon.Actors.User - - @doc """ - Returns the list of users. - - ## Examples - - iex> list_users() - [%Mobilizon.Actors.User{}] - - """ - def list_users(page \\ nil, limit \\ nil, sort \\ nil, direction \\ nil) do - Repo.all( - User - |> paginate(page, limit) - |> sort(sort, direction) - ) - end - - def count_users() do - Repo.one( - from( - u in User, - select: count(u.id) - ) - ) - end - def insert_or_update_actor(data, preload \\ false) do cs = Actor.remote_actor_creation(data) @@ -313,52 +252,6 @@ defmodule Mobilizon.Actors do # update_and_set_cache(cs) # end - @doc """ - Gets a single user. - - Raises `Ecto.NoResultsError` if the User does not exist. - - ## Examples - - iex> get_user!(123) - %Mobilizon.Actors.User{} - - iex> get_user!(456) - ** (Ecto.NoResultsError) - - """ - def get_user!(id), do: Repo.get!(User, id) - - @doc """ - Get an user with it's actors - - Raises `Ecto.NoResultsError` if the User does not exist. - """ - @spec get_user_with_actors!(integer()) :: User.t() - def get_user_with_actors!(id) do - user = Repo.get!(User, id) - Repo.preload(user, [:actors, :default_actor]) - end - - @doc """ - Get user with it's actors by ID - """ - @spec get_user_with_actors(integer()) :: User.t() - def get_user_with_actors(id) do - case Repo.get(User, id) do - nil -> - {:error, "User with ID #{id} not found"} - - user -> - user = - user - |> Repo.preload([:actors, :default_actor]) - |> Map.put(:actors, get_actors_for_user(user)) - - {:ok, user} - end - end - @doc """ Get an actor by it's URL (ActivityPub ID). The `:preload` option allows preloading the Followers relation. @@ -596,22 +489,6 @@ defmodule Mobilizon.Actors do end end - @doc """ - Authenticate user - """ - def authenticate(%{user: user, password: password}) do - # Does password match the one stored in the database? - case Argon2.verify_pass(password, user.password_hash) do - true -> - # Yes, create and return the token - MobilizonWeb.Guardian.encode_and_sign(user) - - _ -> - # No, return an error - {:error, :unauthorized} - end - end - @doc """ Create a new RSA key """ @@ -668,136 +545,6 @@ defmodule Mobilizon.Actors do end end - @doc """ - Creates a user. - - ## Examples - - iex> create_user(%{email: "test@test.tld"}) - {:ok, %Mobilizon.Actors.User{}} - - iex> create_user(%{email: "not an email"}) - {:error, %Ecto.Changeset{}} - - """ - def create_user(attrs \\ %{}) do - %User{} - |> User.registration_changeset(attrs) - |> Repo.insert() - end - - @doc """ - Gets an user by it's email - - ## Examples - - iex> get_user_by_email("test@test.tld", true) - {:ok, %Mobilizon.Actors.User{}} - - iex> get_user_by_email("test@notfound.tld", false) - {:error, :user_not_found} - """ - def get_user_by_email(email, activated \\ nil) do - query = - case activated do - nil -> - from(u in User, where: u.email == ^email, preload: :default_actor) - - true -> - from(u in User, - where: u.email == ^email and not is_nil(u.confirmed_at), - preload: :default_actor - ) - - false -> - from(u in User, - where: u.email == ^email and is_nil(u.confirmed_at), - preload: :default_actor - ) - end - - case Repo.one(query) do - nil -> {:error, :user_not_found} - user -> {:ok, user} - end - end - - @doc """ - Get an user by it's activation token - """ - @spec get_user_by_activation_token(String.t()) :: Actor.t() - def get_user_by_activation_token(token) do - Repo.one( - from(u in User, - where: u.confirmation_token == ^token, - preload: [:default_actor] - ) - ) - end - - @doc """ - Get an user by it's reset password token - """ - @spec get_user_by_reset_password_token(String.t()) :: Actor.t() - def get_user_by_reset_password_token(token) do - Repo.one( - from(u in User, - where: u.reset_password_token == ^token, - preload: [:default_actor] - ) - ) - end - - @doc """ - Updates a user. - - ## Examples - - iex> update_user(User{}, %{password: "coucou"}) - {:ok, %Mobilizon.Actors.User{}} - - iex> update_user(User{}, %{password: nil}) - {:error, %Ecto.Changeset{}} - - """ - def update_user(%User{} = user, attrs) do - with {:ok, %User{} = user} <- - user - |> User.changeset(attrs) - |> Repo.update() do - {:ok, Repo.preload(user, [:default_actor])} - end - end - - @doc """ - Deletes a User. - - ## Examples - - iex> delete_user(%User{email: "test@test.tld"}) - {:ok, %Mobilizon.Actors.User{}} - - iex> delete_user(%User{}) - {:error, %Ecto.Changeset{}} - - """ - def delete_user(%User{} = user) do - Repo.delete(user) - end - - # @doc """ - # Returns an `%Ecto.Changeset{}` for tracking user changes. - - # ## Examples - - # iex> change_user(%Mobilizon.Actors.User{}) - # %Ecto.Changeset{data: %Mobilizon.Actors.User{}} - - # """ - # def change_user(%User{} = user) do - # User.changeset(user, %{}) - # end - alias Mobilizon.Actors.Member @doc """ diff --git a/lib/mobilizon/actors/bot.ex b/lib/mobilizon/actors/bot.ex index b259ffc11..cde23e939 100644 --- a/lib/mobilizon/actors/bot.ex +++ b/lib/mobilizon/actors/bot.ex @@ -4,7 +4,8 @@ defmodule Mobilizon.Actors.Bot do """ use Ecto.Schema import Ecto.Changeset - alias Mobilizon.Actors.{Actor, User} + alias Mobilizon.Actors.Actor + alias Mobilizon.Users.User schema "bots" do field(:source, :string) diff --git a/lib/mobilizon/email/user.ex b/lib/mobilizon/email/user.ex index 35dc8be5e..72268b16e 100644 --- a/lib/mobilizon/email/user.ex +++ b/lib/mobilizon/email/user.ex @@ -2,7 +2,7 @@ defmodule Mobilizon.Email.User do @moduledoc """ Handles emails sent to users """ - alias Mobilizon.Actors.User + alias Mobilizon.Users.User import Bamboo.Email import Bamboo.Phoenix diff --git a/lib/mobilizon/actors/service/activation.ex b/lib/mobilizon/users/service/activation.ex similarity index 78% rename from lib/mobilizon/actors/service/activation.ex rename to lib/mobilizon/users/service/activation.ex index 250d16340..b488de05a 100644 --- a/lib/mobilizon/actors/service/activation.ex +++ b/lib/mobilizon/users/service/activation.ex @@ -1,17 +1,18 @@ -defmodule Mobilizon.Actors.Service.Activation do +defmodule Mobilizon.Users.Service.Activation do @moduledoc false - alias Mobilizon.{Mailer, Actors.User, Actors} + alias Mobilizon.{Mailer, Users} + alias Mobilizon.Users.User alias Mobilizon.Email.User, as: UserEmail - alias Mobilizon.Actors.Service.Tools + alias Mobilizon.Users.Service.Tools require Logger @doc false def check_confirmation_token(token) when is_binary(token) do - with %User{} = user <- Actors.get_user_by_activation_token(token), + with %User{} = user <- Users.get_user_by_activation_token(token), {:ok, %User{} = user} <- - Actors.update_user(user, %{ + Users.update_user(user, %{ "confirmed_at" => DateTime.utc_now() |> DateTime.truncate(:second), "confirmation_sent_at" => nil, "confirmation_token" => nil @@ -27,7 +28,7 @@ defmodule Mobilizon.Actors.Service.Activation do def resend_confirmation_email(%User{} = user, locale \\ "en") do with :ok <- Tools.we_can_send_email(user, :confirmation_sent_at), {:ok, user} <- - Actors.update_user(user, %{ + Users.update_user(user, %{ "confirmation_sent_at" => DateTime.utc_now() |> DateTime.truncate(:second) }) do send_confirmation_email(user, locale) diff --git a/lib/mobilizon/actors/service/reset_password.ex b/lib/mobilizon/users/service/reset_password.ex similarity index 88% rename from lib/mobilizon/actors/service/reset_password.ex rename to lib/mobilizon/users/service/reset_password.ex index f6604fba1..fd04fdfa2 100644 --- a/lib/mobilizon/actors/service/reset_password.ex +++ b/lib/mobilizon/users/service/reset_password.ex @@ -1,18 +1,19 @@ -defmodule Mobilizon.Actors.Service.ResetPassword do +defmodule Mobilizon.Users.Service.ResetPassword do @moduledoc false require Logger - alias Mobilizon.{Mailer, Repo, Actors.User, Actors} + alias Mobilizon.Users.User + alias Mobilizon.{Mailer, Repo, Users} alias Mobilizon.Email.User, as: UserEmail - alias Mobilizon.Actors.Service.Tools + alias Mobilizon.Users.Service.Tools @doc """ Check that the provided token is correct and update provided password """ @spec check_reset_password_token(String.t(), String.t()) :: tuple def check_reset_password_token(password, token) do - with %User{} = user <- Actors.get_user_by_reset_password_token(token), + with %User{} = user <- Users.get_user_by_reset_password_token(token), {:ok, %User{} = user} <- Repo.update( User.password_reset_changeset(user, %{ diff --git a/lib/mobilizon/actors/service/tools.ex b/lib/mobilizon/users/service/tools.ex similarity index 90% rename from lib/mobilizon/actors/service/tools.ex rename to lib/mobilizon/users/service/tools.ex index 2383cd7e3..691ee5eba 100644 --- a/lib/mobilizon/actors/service/tools.ex +++ b/lib/mobilizon/users/service/tools.ex @@ -1,8 +1,8 @@ -defmodule Mobilizon.Actors.Service.Tools do +defmodule Mobilizon.Users.Service.Tools do @moduledoc """ Common functions for actors services """ - alias Mobilizon.Actors.User + alias Mobilizon.Users.User @spec we_can_send_email(User.t(), atom()) :: :ok | {:error, :email_too_soon} def we_can_send_email(%User{} = user, key \\ :reset_password_sent_at) do diff --git a/lib/mobilizon/actors/user.ex b/lib/mobilizon/users/user.ex similarity index 97% rename from lib/mobilizon/actors/user.ex rename to lib/mobilizon/users/user.ex index ad6441bd9..bc271cba9 100644 --- a/lib/mobilizon/actors/user.ex +++ b/lib/mobilizon/users/user.ex @@ -1,10 +1,11 @@ -defmodule Mobilizon.Actors.User do +defmodule Mobilizon.Users.User do @moduledoc """ Represents a local user """ use Ecto.Schema import Ecto.Changeset - alias Mobilizon.Actors.{Actor, User} + alias Mobilizon.Actors.Actor + alias Mobilizon.Users.User alias Mobilizon.Service.EmailChecker schema "users" do diff --git a/lib/mobilizon/users/users.ex b/lib/mobilizon/users/users.ex new file mode 100644 index 000000000..719e824c4 --- /dev/null +++ b/lib/mobilizon/users/users.ex @@ -0,0 +1,257 @@ +defmodule Mobilizon.Users do + @moduledoc """ + The Users context. + """ + + import Ecto.Query, warn: false + + alias Mobilizon.Repo + import Mobilizon.Ecto + + alias Mobilizon.Actors.Actor + alias Mobilizon.Users.User + + @doc false + def data() do + Dataloader.Ecto.new(Repo, query: &query/2) + end + + @doc false + def query(queryable, _params) do + queryable + end + + @doc """ + Gets an user by it's email + + ## Examples + + iex> get_user_by_email("test@test.tld", true) + {:ok, %Mobilizon.Users.User{}} + + iex> get_user_by_email("test@notfound.tld", false) + {:error, :user_not_found} + """ + def get_user_by_email(email, activated \\ nil) do + query = + case activated do + nil -> + from(u in User, where: u.email == ^email, preload: :default_actor) + + true -> + from(u in User, + where: u.email == ^email and not is_nil(u.confirmed_at), + preload: :default_actor + ) + + false -> + from(u in User, + where: u.email == ^email and is_nil(u.confirmed_at), + preload: :default_actor + ) + end + + case Repo.one(query) do + nil -> {:error, :user_not_found} + user -> {:ok, user} + end + end + + @doc """ + Get an user by it's activation token + """ + @spec get_user_by_activation_token(String.t()) :: Actor.t() + def get_user_by_activation_token(token) do + Repo.one( + from(u in User, + where: u.confirmation_token == ^token, + preload: [:default_actor] + ) + ) + end + + @doc """ + Get an user by it's reset password token + """ + @spec get_user_by_reset_password_token(String.t()) :: Actor.t() + def get_user_by_reset_password_token(token) do + Repo.one( + from(u in User, + where: u.reset_password_token == ^token, + preload: [:default_actor] + ) + ) + end + + @doc """ + Updates a user. + + ## Examples + + iex> update_user(User{}, %{password: "coucou"}) + {:ok, %Mobilizon.Users.User{}} + + iex> update_user(User{}, %{password: nil}) + {:error, %Ecto.Changeset{}} + + """ + def update_user(%User{} = user, attrs) do + with {:ok, %User{} = user} <- + user + |> User.changeset(attrs) + |> Repo.update() do + {:ok, Repo.preload(user, [:default_actor])} + end + end + + @doc """ + Deletes a User. + + ## Examples + + iex> delete_user(%User{email: "test@test.tld"}) + {:ok, %Mobilizon.Users.User{}} + + iex> delete_user(%User{}) + {:error, %Ecto.Changeset{}} + + """ + def delete_user(%User{} = user) do + Repo.delete(user) + end + + # @doc """ + # Returns an `%Ecto.Changeset{}` for tracking user changes. + + # ## Examples + + # iex> change_user(%Mobilizon.Users.User{}) + # %Ecto.Changeset{data: %Mobilizon.Users.User{}} + + # """ + # def change_user(%User{} = user) do + # User.changeset(user, %{}) + # end + + @doc """ + Gets a single user. + + Raises `Ecto.NoResultsError` if the User does not exist. + + ## Examples + + iex> get_user!(123) + %Mobilizon.Users.User{} + + iex> get_user!(456) + ** (Ecto.NoResultsError) + + """ + def get_user!(id), do: Repo.get!(User, id) + + @doc """ + Get an user with it's actors + + Raises `Ecto.NoResultsError` if the User does not exist. + """ + @spec get_user_with_actors!(integer()) :: User.t() + def get_user_with_actors!(id) do + user = Repo.get!(User, id) + Repo.preload(user, [:actors, :default_actor]) + end + + @doc """ + Get user with it's actors by ID + """ + @spec get_user_with_actors(integer()) :: User.t() + def get_user_with_actors(id) do + case Repo.get(User, id) do + nil -> + {:error, "User with ID #{id} not found"} + + user -> + user = + user + |> Repo.preload([:actors, :default_actor]) + |> Map.put(:actors, get_actors_for_user(user)) + + {:ok, user} + end + end + + @doc """ + Returns the associated actor for an user, either the default set one or the first found + """ + @spec get_actor_for_user(Mobilizon.Users.User.t()) :: Mobilizon.Actors.Actor.t() + def get_actor_for_user(%Mobilizon.Users.User{} = user) do + case Repo.one( + from(a in Actor, + join: u in User, + on: u.default_actor_id == a.id, + where: u.id == ^user.id + ) + ) do + nil -> + case user |> get_actors_for_user() do + [] -> nil + actors -> hd(actors) + end + + actor -> + actor + end + end + + def get_actors_for_user(%User{id: user_id}) do + Repo.all(from(a in Actor, where: a.user_id == ^user_id)) + end + + @doc """ + Authenticate user + """ + def authenticate(%{user: user, password: password}) do + # Does password match the one stored in the database? + case Argon2.verify_pass(password, user.password_hash) do + true -> + # Yes, create and return the token + MobilizonWeb.Guardian.encode_and_sign(user) + + _ -> + # No, return an error + {:error, :unauthorized} + end + end + + def update_user_default_actor(user_id, actor_id) do + with from(u in User, where: u.id == ^user_id, update: [set: [default_actor_id: ^actor_id]]) + |> Repo.update_all([]) do + Repo.get!(User, user_id) |> Repo.preload([:default_actor]) + end + end + + @doc """ + Returns the list of users. + + ## Examples + + iex> list_users() + [%Mobilizon.Users.User{}] + + """ + def list_users(page \\ nil, limit \\ nil, sort \\ nil, direction \\ nil) do + Repo.all( + User + |> paginate(page, limit) + |> sort(sort, direction) + ) + end + + def count_users() do + Repo.one( + from( + u in User, + select: count(u.id) + ) + ) + end +end diff --git a/lib/mobilizon_web/controllers/node_info_controller.ex b/lib/mobilizon_web/controllers/node_info_controller.ex index ab8b5e073..c20b8292e 100644 --- a/lib/mobilizon_web/controllers/node_info_controller.ex +++ b/lib/mobilizon_web/controllers/node_info_controller.ex @@ -6,7 +6,7 @@ defmodule MobilizonWeb.NodeInfoController do use MobilizonWeb, :controller - alias Mobilizon.{Actors, Events} + alias Mobilizon.{Events, Users} @instance Application.get_env(:mobilizon, :instance) @@ -39,7 +39,7 @@ defmodule MobilizonWeb.NodeInfoController do openRegistrations: Keyword.get(@instance, :registrations_open), usage: %{ users: %{ - total: Actors.count_users() + total: Users.count_users() }, localPosts: Events.count_local_events(), localComments: Events.count_local_comments() diff --git a/lib/mobilizon_web/guardian.ex b/lib/mobilizon_web/guardian.ex index 3d02c3d43..1574b8d23 100644 --- a/lib/mobilizon_web/guardian.ex +++ b/lib/mobilizon_web/guardian.ex @@ -9,8 +9,8 @@ defmodule MobilizonWeb.Guardian do user: [:base] } - alias Mobilizon.Actors - alias Mobilizon.Actors.User + alias Mobilizon.Users + alias Mobilizon.Users.User def subject_for_token(%User{} = user, _claims) do {:ok, "User:" <> to_string(user.id)} @@ -24,7 +24,7 @@ defmodule MobilizonWeb.Guardian do try do case Integer.parse(uid_str) do {uid, ""} -> - {:ok, Actors.get_user_with_actors!(uid)} + {:ok, Users.get_user_with_actors!(uid)} _ -> {:error, :invalid_id} diff --git a/lib/mobilizon_web/resolvers/comment.ex b/lib/mobilizon_web/resolvers/comment.ex index 2fd740081..09d866bf3 100644 --- a/lib/mobilizon_web/resolvers/comment.ex +++ b/lib/mobilizon_web/resolvers/comment.ex @@ -5,7 +5,7 @@ defmodule MobilizonWeb.Resolvers.Comment do require Logger alias Mobilizon.Events.Comment alias Mobilizon.Activity - alias Mobilizon.Actors.User + alias Mobilizon.Users.User alias MobilizonWeb.API.Comments def create_comment(_parent, %{text: comment, actor_username: username}, %{ diff --git a/lib/mobilizon_web/resolvers/event.ex b/lib/mobilizon_web/resolvers/event.ex index b070af136..b1ce90944 100644 --- a/lib/mobilizon_web/resolvers/event.ex +++ b/lib/mobilizon_web/resolvers/event.ex @@ -4,7 +4,8 @@ defmodule MobilizonWeb.Resolvers.Event do """ alias Mobilizon.Activity alias Mobilizon.Events.{Event, Participant} - alias Mobilizon.Actors.{User, Actor} + alias Mobilizon.Actors.Actor + alias Mobilizon.Users.User # We limit the max number of events that can be retrieved @event_max_limit 100 diff --git a/lib/mobilizon_web/resolvers/group.ex b/lib/mobilizon_web/resolvers/group.ex index 3cc92d8b4..f104e3d27 100644 --- a/lib/mobilizon_web/resolvers/group.ex +++ b/lib/mobilizon_web/resolvers/group.ex @@ -3,7 +3,8 @@ defmodule MobilizonWeb.Resolvers.Group do Handles the group-related GraphQL calls """ alias Mobilizon.Actors - alias Mobilizon.Actors.{Actor, User, Member} + alias Mobilizon.Actors.{Actor, Member} + alias Mobilizon.Users.User alias Mobilizon.Service.ActivityPub alias Mobilizon.Activity require Logger diff --git a/lib/mobilizon_web/resolvers/person.ex b/lib/mobilizon_web/resolvers/person.ex index 819a78930..43725edde 100644 --- a/lib/mobilizon_web/resolvers/person.ex +++ b/lib/mobilizon_web/resolvers/person.ex @@ -3,7 +3,9 @@ defmodule MobilizonWeb.Resolvers.Person do Handles the person-related GraphQL calls """ alias Mobilizon.Actors - alias Mobilizon.Actors.{Actor, User} + alias Mobilizon.Actors.Actor + alias Mobilizon.Users.User + alias Mobilizon.Users alias Mobilizon.Service.ActivityPub @doc """ @@ -23,7 +25,7 @@ defmodule MobilizonWeb.Resolvers.Person do Returns the current actor for the currently logged-in user """ def get_current_person(_parent, _args, %{context: %{current_user: user}}) do - {:ok, Actors.get_actor_for_user(user)} + {:ok, Users.get_actor_for_user(user)} end def get_current_person(_parent, _args, _resolution) do @@ -34,7 +36,7 @@ defmodule MobilizonWeb.Resolvers.Person do Returns the list of identities for the logged-in user """ def identities(_parent, _args, %{context: %{current_user: user}}) do - {:ok, Actors.get_actors_for_user(user)} + {:ok, Users.get_actors_for_user(user)} end def identities(_parent, _args, _resolution) do @@ -65,8 +67,8 @@ defmodule MobilizonWeb.Resolvers.Person do This function is used to register a person afterwards the user has been created (but not activated) """ def register_person(_parent, args, _resolution) do - with {:ok, %User{} = user} <- Actors.get_user_by_email(args.email), - {:no_actor, nil} <- {:no_actor, Actors.get_actor_for_user(user)}, + with {:ok, %User{} = user} <- Users.get_user_by_email(args.email), + {:no_actor, nil} <- {:no_actor, Users.get_actor_for_user(user)}, args <- Map.put(args, :user_id, user.id), {:ok, %Actor{} = new_person} <- Actors.new_person(args) do {:ok, new_person} diff --git a/lib/mobilizon_web/resolvers/user.ex b/lib/mobilizon_web/resolvers/user.ex index 3b3786803..afbfc8098 100644 --- a/lib/mobilizon_web/resolvers/user.ex +++ b/lib/mobilizon_web/resolvers/user.ex @@ -2,15 +2,17 @@ defmodule MobilizonWeb.Resolvers.User do @moduledoc """ Handles the user-related GraphQL calls """ - alias Mobilizon.Actors.{User, Actor} - alias Mobilizon.Actors + alias Mobilizon.Actors.Actor + alias Mobilizon.Users.User + alias Mobilizon.{Actors, Users} + alias Mobilizon.Users.Service.{ResetPassword, Activation} require Logger @doc """ Find an user by it's ID """ def find_user(_parent, %{id: id}, _resolution) do - Actors.get_user_with_actors(id) + Users.get_user_with_actors(id) end @doc """ @@ -32,8 +34,8 @@ defmodule MobilizonWeb.Resolvers.User do %{page: page, limit: limit, sort: sort, direction: direction}, _resolution ) do - total = Task.async(&Actors.count_users/0) - elements = Task.async(fn -> Actors.list_users(page, limit, sort, direction) end) + total = Task.async(&Users.count_users/0) + elements = Task.async(fn -> Users.list_users(page, limit, sort, direction) end) {:ok, %{total: Task.await(total), elements: Task.await(elements)}} end @@ -42,8 +44,8 @@ defmodule MobilizonWeb.Resolvers.User do Login an user. Returns a token and the user """ def login_user(_parent, %{email: email, password: password}, _resolution) do - with {:ok, %User{} = user} <- Actors.get_user_by_email(email, true), - {:ok, token, _} <- Actors.authenticate(%{user: user, password: password}) do + with {:ok, %User{} = user} <- Users.get_user_by_email(email, true), + {:ok, token, _} <- Users.authenticate(%{user: user, password: password}) do {:ok, %{token: token, user: user}} else {:error, :user_not_found} -> @@ -62,7 +64,7 @@ defmodule MobilizonWeb.Resolvers.User do @spec create_user(any(), map(), any()) :: tuple() def create_user(_parent, args, _resolution) do with {:ok, %User{} = user} <- Actors.register(args) do - Mobilizon.Actors.Service.Activation.send_confirmation_email(user) + Activation.send_confirmation_email(user) {:ok, user} end end @@ -72,9 +74,8 @@ defmodule MobilizonWeb.Resolvers.User do """ def validate_user(_parent, %{token: token}, _resolution) do with {:check_confirmation_token, {:ok, %User{} = user}} <- - {:check_confirmation_token, - Mobilizon.Actors.Service.Activation.check_confirmation_token(token)}, - {:get_actor, actor} <- {:get_actor, Actors.get_actor_for_user(user)}, + {:check_confirmation_token, Activation.check_confirmation_token(token)}, + {:get_actor, actor} <- {:get_actor, Users.get_actor_for_user(user)}, {:guardian_encode_and_sign, {:ok, token, _}} <- {:guardian_encode_and_sign, MobilizonWeb.Guardian.encode_and_sign(user)} do {:ok, %{token: token, user: Map.put(user, :default_actor, actor)}} @@ -91,9 +92,9 @@ defmodule MobilizonWeb.Resolvers.User do We only do this to accounts unconfirmed """ def resend_confirmation_email(_parent, %{email: email, locale: locale}, _resolution) do - with {:ok, user} <- Actors.get_user_by_email(email, false), + with {:ok, user} <- Users.get_user_by_email(email, false), {:ok, email} <- - Mobilizon.Actors.Service.Activation.resend_confirmation_email(user, locale) do + Activation.resend_confirmation_email(user, locale) do {:ok, email} else {:error, :user_not_found} -> @@ -108,9 +109,9 @@ defmodule MobilizonWeb.Resolvers.User do Send an email to reset the password from an user """ def send_reset_password(_parent, %{email: email, locale: locale}, _resolution) do - with {:ok, user} <- Actors.get_user_by_email(email, true), + with {:ok, user} <- Users.get_user_by_email(email, true), {:ok, %Bamboo.Email{} = _email_html} <- - Mobilizon.Actors.Service.ResetPassword.send_password_reset_email(user, locale) do + ResetPassword.send_password_reset_email(user, locale) do {:ok, email} else {:error, :user_not_found} -> @@ -127,7 +128,7 @@ defmodule MobilizonWeb.Resolvers.User do """ def reset_password(_parent, %{password: password, token: token}, _resolution) do with {:ok, %User{} = user} <- - Mobilizon.Actors.Service.ResetPassword.check_reset_password_token(password, token), + ResetPassword.check_reset_password_token(password, token), {:ok, token, _} <- MobilizonWeb.Guardian.encode_and_sign(user) do {:ok, %{token: token, user: user}} end @@ -139,8 +140,8 @@ defmodule MobilizonWeb.Resolvers.User do }) do with %Actor{id: actor_id} <- Actors.get_local_actor_by_name(username), {:user_actor, true} <- - {:user_actor, actor_id in Enum.map(Actors.get_actors_for_user(user), & &1.id)}, - %User{} = user <- Actors.update_user_default_actor(user.id, actor_id) do + {:user_actor, actor_id in Enum.map(Users.get_actors_for_user(user), & &1.id)}, + %User{} = user <- Users.update_user_default_actor(user.id, actor_id) do {:ok, user} else {:user_actor, _} -> diff --git a/test/mobilizon/actors/actors_test.exs b/test/mobilizon/actors/actors_test.exs index b4c3fabba..70192c111 100644 --- a/test/mobilizon/actors/actors_test.exs +++ b/test/mobilizon/actors/actors_test.exs @@ -2,7 +2,8 @@ defmodule Mobilizon.ActorsTest do use Mobilizon.DataCase alias Mobilizon.Actors - alias Mobilizon.Actors.{Actor, Member, Follower, User, Bot} + alias Mobilizon.Actors.{Actor, Member, Follower, Bot} + alias Mobilizon.Users import Mobilizon.Factory use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney @@ -60,13 +61,13 @@ defmodule Mobilizon.ActorsTest do test "get_actor_for_user/1 returns the actor for an user", %{ actor: %{user: user, id: actor_id} = _actor } do - assert actor_id == Actors.get_actor_for_user(user).id + assert actor_id == Users.get_actor_for_user(user).id end test "get_actor_for_user/1 returns the actor for an user with no default actor defined" do user = insert(:user) actor_id = insert(:actor, user: user).id - assert actor_id == Actors.get_actor_for_user(user).id + assert actor_id == Users.get_actor_for_user(user).id end test "get_actor_with_everything/1 returns the actor with it's organized events", %{ @@ -265,104 +266,6 @@ defmodule Mobilizon.ActorsTest do end end - describe "users" do - alias Mobilizon.Actors.{User, Actor} - - @valid_attrs %{email: "foo@bar.tld", password: "some password"} - @update_attrs %{email: "foo@fighters.tld", password: "some updated password"} - @invalid_attrs %{email: nil, password: nil} - - test "list_users/0 returns all users" do - user = insert(:user) - users = Actors.list_users(nil, nil, :id, :desc) - assert [user.id] == users |> Enum.map(& &1.id) - end - - test "get_user!/1 returns the user with given id" do - user = insert(:user) - assert user = Actors.get_user!(user.id) - end - - # There's no create_user/1, just register/1 - test "register/1 with valid data creates a user" do - assert {:ok, %User{email: email} = user} = Actors.register(@valid_attrs) - - assert email == @valid_attrs.email - end - - test "create_user/1 with invalid data returns error changeset" do - assert {:error, - %Ecto.Changeset{ - errors: [ - password: {"can't be blank", [validation: :required]}, - email: {"can't be blank", [validation: :required]} - ], - valid?: false - }} = Actors.register(@invalid_attrs) - end - - test "update_user/2 with valid data updates the user" do - user = insert(:user) - assert {:ok, %User{email: email}} = Actors.update_user(user, @update_attrs) - assert email == "foo@fighters.tld" - end - - test "update_user/2 with invalid data returns error changeset" do - user = insert(:user) - assert {:error, %Ecto.Changeset{}} = Actors.update_user(user, @invalid_attrs) - assert user = Actors.get_user!(user.id) - end - - test "delete_user/1 deletes the user" do - user = insert(:user) - assert {:ok, %User{}} = Actors.delete_user(user) - assert_raise Ecto.NoResultsError, fn -> Actors.get_user!(user.id) end - end - - # test "change_user/1 returns a user changeset" do - # user = insert(:user) - # assert %Ecto.Changeset{} = Actors.change_user(user) - # end - - @email "email@domain.tld" - @password "password" - test "authenticate/1 checks the user's password" do - {:ok, %User{} = user} = Actors.register(%{email: @email, password: @password}) - - assert {:ok, _, _} = Actors.authenticate(%{user: user, password: @password}) - - assert {:error, :unauthorized} == - Actors.authenticate(%{user: user, password: "bad password"}) - end - - test "get_user_by_email/1 finds an user by it's email" do - {:ok, %User{email: email} = user} = Actors.register(%{email: @email, password: @password}) - - assert email == @email - {:ok, %User{id: id}} = Actors.get_user_by_email(@email) - assert id == user.id - assert {:error, :user_not_found} = Actors.get_user_by_email("no email") - end - - test "get_user_by_email/1 finds an activated user by it's email" do - {:ok, %User{} = user} = Actors.register(%{email: @email, password: @password}) - - {:ok, %User{id: id}} = Actors.get_user_by_email(@email, false) - assert id == user.id - assert {:error, :user_not_found} = Actors.get_user_by_email(@email, true) - - Actors.update_user(user, %{ - "confirmed_at" => DateTime.utc_now() |> DateTime.truncate(:second), - "confirmation_sent_at" => nil, - "confirmation_token" => nil - }) - - assert {:error, :user_not_found} = Actors.get_user_by_email(@email, false) - {:ok, %User{id: id}} = Actors.get_user_by_email(@email, true) - assert id == user.id - end - end - describe "groups" do alias Mobilizon.Actors alias Mobilizon.Actors.Actor diff --git a/test/mobilizon/users/users_test.exs b/test/mobilizon/users/users_test.exs new file mode 100644 index 000000000..9284b23f1 --- /dev/null +++ b/test/mobilizon/users/users_test.exs @@ -0,0 +1,105 @@ +defmodule Mobilizon.UsersTest do + use Mobilizon.DataCase + + alias Mobilizon.Actors + alias Mobilizon.Users + alias Mobilizon.Users.User + import Mobilizon.Factory + use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney + + describe "users" do + @valid_attrs %{email: "foo@bar.tld", password: "some password"} + @update_attrs %{email: "foo@fighters.tld", password: "some updated password"} + @invalid_attrs %{email: nil, password: nil} + + test "list_users/0 returns all users" do + user = insert(:user) + users = Users.list_users(nil, nil, :id, :desc) + assert [user.id] == users |> Enum.map(& &1.id) + end + + test "get_user!/1 returns the user with given id" do + user = insert(:user) + assert user = Users.get_user!(user.id) + end + + # There's no create_user/1, just register/1 + test "register/1 with valid data creates a user" do + assert {:ok, %User{email: email} = user} = Actors.register(@valid_attrs) + + assert email == @valid_attrs.email + end + + test "create_user/1 with invalid data returns error changeset" do + assert {:error, + %Ecto.Changeset{ + errors: [ + password: {"can't be blank", [validation: :required]}, + email: {"can't be blank", [validation: :required]} + ], + valid?: false + }} = Actors.register(@invalid_attrs) + end + + test "update_user/2 with valid data updates the user" do + user = insert(:user) + assert {:ok, %User{email: email}} = Users.update_user(user, @update_attrs) + assert email == "foo@fighters.tld" + end + + test "update_user/2 with invalid data returns error changeset" do + user = insert(:user) + assert {:error, %Ecto.Changeset{}} = Users.update_user(user, @invalid_attrs) + assert user = Users.get_user!(user.id) + end + + test "delete_user/1 deletes the user" do + user = insert(:user) + assert {:ok, %User{}} = Users.delete_user(user) + assert_raise Ecto.NoResultsError, fn -> Users.get_user!(user.id) end + end + + # test "change_user/1 returns a user changeset" do + # user = insert(:user) + # assert %Ecto.Changeset{} = Users.change_user(user) + # end + + @email "email@domain.tld" + @password "password" + test "authenticate/1 checks the user's password" do + {:ok, %User{} = user} = Actors.register(%{email: @email, password: @password}) + + assert {:ok, _, _} = Users.authenticate(%{user: user, password: @password}) + + assert {:error, :unauthorized} == + Users.authenticate(%{user: user, password: "bad password"}) + end + + test "get_user_by_email/1 finds an user by it's email" do + {:ok, %User{email: email} = user} = Actors.register(%{email: @email, password: @password}) + + assert email == @email + {:ok, %User{id: id}} = Users.get_user_by_email(@email) + assert id == user.id + assert {:error, :user_not_found} = Users.get_user_by_email("no email") + end + + test "get_user_by_email/1 finds an activated user by it's email" do + {:ok, %User{} = user} = Actors.register(%{email: @email, password: @password}) + + {:ok, %User{id: id}} = Users.get_user_by_email(@email, false) + assert id == user.id + assert {:error, :user_not_found} = Users.get_user_by_email(@email, true) + + Users.update_user(user, %{ + "confirmed_at" => DateTime.utc_now() |> DateTime.truncate(:second), + "confirmation_sent_at" => nil, + "confirmation_token" => nil + }) + + assert {:error, :user_not_found} = Users.get_user_by_email(@email, false) + {:ok, %User{id: id}} = Users.get_user_by_email(@email, true) + assert id == user.id + end + end +end diff --git a/test/mobilizon_web/resolvers/user_resolver_test.exs b/test/mobilizon_web/resolvers/user_resolver_test.exs index f38e866b2..186452734 100644 --- a/test/mobilizon_web/resolvers/user_resolver_test.exs +++ b/test/mobilizon_web/resolvers/user_resolver_test.exs @@ -1,8 +1,10 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do use MobilizonWeb.ConnCase - alias Mobilizon.Actors - alias Mobilizon.Actors.{User, Actor} + alias Mobilizon.{Actors, Users} + alias Mobilizon.Actors.Actor + alias Mobilizon.Users.User alias MobilizonWeb.AbsintheHelpers + alias Mobilizon.Users.Service.ResetPassword import Mobilizon.Factory use Bamboo.Test @@ -431,7 +433,7 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do "You requested again a confirmation email too soon" # Hammer time ! - Mobilizon.Actors.update_user(user, %{ + Mobilizon.Users.update_user(user, %{ confirmation_sent_at: Timex.shift(user.confirmation_sent_at, hours: -3) }) @@ -503,8 +505,8 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do test "test reset_password/3 with valid email", context do {:ok, %User{} = user} = Actors.register(%{email: "toto@tata.tld", password: "p4ssw0rd"}) %Actor{} = insert(:actor, user: user) - {:ok, _email_sent} = Mobilizon.Actors.Service.ResetPassword.send_password_reset_email(user) - %User{reset_password_token: reset_password_token} = Mobilizon.Actors.get_user!(user.id) + {:ok, _email_sent} = ResetPassword.send_password_reset_email(user) + %User{reset_password_token: reset_password_token} = Mobilizon.Users.get_user!(user.id) mutation = """ mutation { @@ -528,8 +530,8 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do test "test reset_password/3 with a password too short", context do %User{} = user = insert(:user) - {:ok, _email_sent} = Mobilizon.Actors.Service.ResetPassword.send_password_reset_email(user) - %User{reset_password_token: reset_password_token} = Mobilizon.Actors.get_user!(user.id) + {:ok, _email_sent} = ResetPassword.send_password_reset_email(user) + %User{reset_password_token: reset_password_token} = Mobilizon.Users.get_user!(user.id) mutation = """ mutation { @@ -554,8 +556,8 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do test "test reset_password/3 with an invalid token", context do %User{} = user = insert(:user) - {:ok, _email_sent} = Mobilizon.Actors.Service.ResetPassword.send_password_reset_email(user) - %User{} = Mobilizon.Actors.get_user!(user.id) + {:ok, _email_sent} = ResetPassword.send_password_reset_email(user) + %User{} = Mobilizon.Users.get_user!(user.id) mutation = """ mutation { @@ -584,7 +586,7 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do {:ok, %User{} = user} = Actors.register(%{email: "toto@tata.tld", password: "p4ssw0rd"}) {:ok, %User{} = _user} = - Actors.update_user(user, %{ + Users.update_user(user, %{ "confirmed_at" => DateTime.utc_now() |> DateTime.truncate(:second), "confirmation_sent_at" => nil, "confirmation_token" => nil @@ -616,7 +618,7 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do {:ok, %User{} = user} = Actors.register(%{email: "toto@tata.tld", password: "p4ssw0rd"}) {:ok, %User{} = _user} = - Actors.update_user(user, %{ + Users.update_user(user, %{ "confirmed_at" => DateTime.utc_now() |> DateTime.truncate(:second), "confirmation_sent_at" => nil, "confirmation_token" => nil @@ -677,12 +679,12 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do user = insert(:user) insert(:actor, user: user) - assert {:ok, %User{actors: actors}} = Actors.get_user_with_actors(user.id) + assert {:ok, %User{actors: actors}} = Users.get_user_with_actors(user.id) actor_params = @valid_single_actor_params |> Map.put(:user_id, user.id) assert {:ok, %Actor{} = actor2} = Actors.create_actor(actor_params) - assert {:ok, %User{actors: actors}} = Actors.get_user_with_actors(user.id) + assert {:ok, %User{actors: actors}} = Users.get_user_with_actors(user.id) assert length(actors) == 2 mutation = """ diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index 686bcb921..170d6e9e6 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -24,7 +24,7 @@ defmodule MobilizonWeb.ConnCase do # The default endpoint for testing @endpoint MobilizonWeb.Endpoint - def auth_conn(%Plug.Conn{} = conn, %Mobilizon.Actors.User{} = user) do + def auth_conn(%Plug.Conn{} = conn, %Mobilizon.Users.User{} = user) do {:ok, token, _claims} = MobilizonWeb.Guardian.encode_and_sign(user) conn diff --git a/test/support/factory.ex b/test/support/factory.ex index 13281407e..f3a42b445 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -6,7 +6,7 @@ defmodule Mobilizon.Factory do use ExMachina.Ecto, repo: Mobilizon.Repo def user_factory do - %Mobilizon.Actors.User{ + %Mobilizon.Users.User{ password_hash: "Jane Smith", email: sequence(:email, &"email-#{&1}@example.com"), role: 0,