[#394] Added `users.tags` and admin routes to tag and untag users. Added tests.
authorIvan Tashkinov <ivantashkinov@gmail.com>
Thu, 6 Dec 2018 17:06:50 +0000 (20:06 +0300)
committerIvan Tashkinov <ivantashkinov@gmail.com>
Thu, 6 Dec 2018 17:06:50 +0000 (20:06 +0300)
lib/pleroma/user.ex
lib/pleroma/web/admin_api/admin_api_controller.ex
lib/pleroma/web/controller_helper.ex [new file with mode: 0644]
lib/pleroma/web/mastodon_api/views/account_view.ex
lib/pleroma/web/router.ex
lib/pleroma/web/twitter_api/views/user_view.ex
priv/repo/migrations/20181206125616_add_tags_to_users.exs [new file with mode: 0644]
test/web/admin_api/admin_api_controller_test.exs

index 74ae5ef0d7e30c9905fc32e1ddb21fc78e7d9aeb..24bc808945d02c4619cc1771b5c3bed43604471d 100644 (file)
@@ -23,6 +23,7 @@ defmodule Pleroma.User do
     field(:local, :boolean, default: true)
     field(:follower_address, :string)
     field(:search_distance, :float, virtual: true)
+    field(:tags, {:array, :string}, default: [])
     field(:last_refreshed_at, :naive_datetime)
     has_many(:notifications, Notification)
     embeds_one(:info, Pleroma.User.Info)
@@ -819,4 +820,46 @@ defmodule Pleroma.User do
 
     CommonUtils.format_input(bio, mentions, tags, "text/plain") |> Formatter.emojify(emoji)
   end
+
+  def tag(user_identifiers, tags), do: tag_or_untag(user_identifiers, tags, :tag)
+
+  def untag(user_identifiers, tags), do: tag_or_untag(user_identifiers, tags, :untag)
+
+  defp tag_or_untag(user_identifier, tags, action) when not is_list(user_identifier),
+    do: tag_or_untag([user_identifier], tags, action)
+
+  defp tag_or_untag([hd | _] = nicknames, tags, action) when is_binary(hd) do
+    users = Repo.all(from(u in User, where: u.nickname in ^nicknames))
+
+    if length(users) == length(nicknames) do
+      tag_or_untag(users, tags, action)
+    else
+      {:error, :not_found}
+    end
+  end
+
+  defp tag_or_untag([hd | _] = users, tags, action) when is_map(hd) do
+    tags =
+      [tags]
+      |> List.flatten()
+      |> Enum.map(&String.downcase(&1))
+
+    Repo.transaction(fn ->
+      for user <- users do
+        new_tags =
+          if action == :tag do
+            Enum.uniq(user.tags ++ tags)
+          else
+            user.tags -- tags
+          end
+
+        {:ok, updated_user} =
+          user
+          |> change(%{tags: new_tags})
+          |> Repo.update()
+
+        updated_user
+      end
+    end)
+  end
 end
index 2c67d9cdaf3011d25255fb6b9dd83594a80ea452..0bd85e0b65ce3fedb16d0191956f2b7fcb790550 100644 (file)
@@ -3,6 +3,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
   alias Pleroma.{User, Repo}
   alias Pleroma.Web.ActivityPub.Relay
 
+  import Pleroma.Web.ControllerHelper, only: [json_response: 3]
+
   require Logger
 
   action_fallback(:errors)
@@ -40,6 +42,16 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     |> json(new_user.nickname)
   end
 
+  def tag_users(conn, %{"nicknames" => nicknames, "tags" => tags}) do
+    with {:ok, _} <- User.tag(nicknames, tags),
+         do: json_response(conn, :no_content, "")
+  end
+
+  def untag_users(conn, %{"nicknames" => nicknames, "tags" => tags}) do
+    with {:ok, _} <- User.untag(nicknames, tags),
+         do: json_response(conn, :no_content, "")
+  end
+
   def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname})
       when permission_group in ["moderator", "admin"] do
     user = User.get_by_nickname(nickname)
diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex
new file mode 100644 (file)
index 0000000..ddf9588
--- /dev/null
@@ -0,0 +1,9 @@
+defmodule Pleroma.Web.ControllerHelper do
+  use Pleroma.Web, :controller
+
+  def json_response(conn, status, json) do
+    conn
+    |> put_status(status)
+    |> json(json)
+  end
+end
index bcfa8836e0f3d2a04510728b5dbc62a7956776e8..0add1b6861fb095f1f777717e271f52fe4d570c3 100644 (file)
@@ -58,7 +58,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
         note: "",
         privacy: user_info.default_scope,
         sensitive: false
-      }
+      },
+      # Note: Mastodon does not return this field:
+      tags: user.tags
     }
   end
 
index b7c79d2eb06d1668b8b2fa54937b64371e5853b4..ae942701eea205110f54791db2267f2d8fb75b2b 100644 (file)
@@ -98,6 +98,8 @@ defmodule Pleroma.Web.Router do
     pipe_through(:admin_api)
     delete("/user", AdminAPIController, :user_delete)
     post("/user", AdminAPIController, :user_create)
+    put("/users/tag", AdminAPIController, :tag_users)
+    put("/users/untag", AdminAPIController, :untag_users)
 
     get("/permission_group/:nickname", AdminAPIController, :right_get)
     get("/permission_group/:nickname/:permission_group", AdminAPIController, :right_get)
index b78024ed756b8c96e876d018b05d6a5f8675376b..dae6563726f52398d57bfe71b9be7cce411c21b1 100644 (file)
@@ -77,7 +77,9 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
       "locked" => user.info.locked,
       "default_scope" => user.info.default_scope,
       "no_rich_text" => user.info.no_rich_text,
-      "fields" => fields
+      "fields" => fields,
+      # Note: twitter.com does not return this field:
+      "tags" => user.tags
     }
 
     if assigns[:token] do
diff --git a/priv/repo/migrations/20181206125616_add_tags_to_users.exs b/priv/repo/migrations/20181206125616_add_tags_to_users.exs
new file mode 100644 (file)
index 0000000..1502f63
--- /dev/null
@@ -0,0 +1,11 @@
+defmodule Pleroma.Repo.Migrations.AddTagsToUsers do
+  use Ecto.Migration
+
+  def change do
+    alter table(:users) do
+      add :tags, {:array, :string}
+    end
+
+    create index(:users, [:tags], using: :gin)
+  end
+end
index 9634ad7c5c6bdb70a47d19f961c83fffa9b5136c..6c86ea143852f8b38682b0fcc1c864002f910a49 100644 (file)
@@ -37,6 +37,62 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
   end
 
+  describe "/api/pleroma/admin//users/tag" do
+    setup do
+      admin = insert(:user, info: %{is_admin: true})
+      user1 = insert(:user, %{tags: ["x"]})
+      user2 = insert(:user, %{tags: ["y"]})
+      user3 = insert(:user, %{tags: ["unchanged"]})
+
+      conn =
+        build_conn()
+        |> assign(:user, admin)
+        |> put_req_header("accept", "application/json")
+        |> put("/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=#{user2.nickname}&tags[]=foo&tags[]=bar")
+
+      %{conn: conn, user1: user1, user2: user2, user3: user3}
+    end
+
+    test "it appends specified tags to users with specified nicknames", %{conn: conn, user1: user1, user2: user2} do
+      assert json_response(conn, :no_content)
+      assert Repo.get(User, user1.id).tags == ["x", "foo", "bar"]
+      assert Repo.get(User, user2.id).tags == ["y", "foo", "bar"]
+    end
+
+    test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
+      assert json_response(conn, :no_content)
+      assert Repo.get(User, user3.id).tags == ["unchanged"]
+    end
+  end
+
+  describe "/api/pleroma/admin//users/untag" do
+    setup do
+      admin = insert(:user, info: %{is_admin: true})
+      user1 = insert(:user, %{tags: ["x"]})
+      user2 = insert(:user, %{tags: ["y", "z"]})
+      user3 = insert(:user, %{tags: ["unchanged"]})
+
+      conn =
+        build_conn()
+        |> assign(:user, admin)
+        |> put_req_header("accept", "application/json")
+        |> put("/api/pleroma/admin/users/untag?nicknames[]=#{user1.nickname}&nicknames[]=#{user2.nickname}&tags[]=x&tags[]=z")
+
+      %{conn: conn, user1: user1, user2: user2, user3: user3}
+    end
+
+    test "it removes specified tags from users with specified nicknames", %{conn: conn, user1: user1, user2: user2} do
+      assert json_response(conn, :no_content)
+      assert Repo.get(User, user1.id).tags == []
+      assert Repo.get(User, user2.id).tags == ["y"]
+    end
+
+    test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
+      assert json_response(conn, :no_content)
+      assert Repo.get(User, user3.id).tags == ["unchanged"]
+    end
+  end
+
   describe "/api/pleroma/admin/permission_group" do
     test "GET is giving user_info" do
       admin = insert(:user, info: %{is_admin: true})