From 25b9e7a8c39602e6463a867089948a7957cfab9f Mon Sep 17 00:00:00 2001
From: eugenijm <eugenijm@protonmail.com>
Date: Tue, 19 Feb 2019 18:40:57 +0300
Subject: [PATCH 01/10] Added admin API for changing user activation status

---
 docs/Admin-API.md                             |  8 ++++
 .../web/admin_api/admin_api_controller.ex     |  7 +++
 lib/pleroma/web/router.ex                     |  2 +
 .../admin_api/admin_api_controller_test.exs   | 48 +++++++++++++++++++
 4 files changed, 65 insertions(+)

diff --git a/docs/Admin-API.md b/docs/Admin-API.md
index 3b19d1aa6..016444d58 100644
--- a/docs/Admin-API.md
+++ b/docs/Admin-API.md
@@ -66,6 +66,14 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
     * On success: JSON of the ``user.info``
 * Note: An admin cannot revoke their own admin status.
 
+## `/api/pleroma/admin/activation_status/:nickname`
+
+### Active or deactivate a user
+* Method: `PUT`
+* Params:
+    * `nickname`
+    * `status` BOOLEAN field, false value means deactivation.
+
 ## `/api/pleroma/admin/relay`
 ### Follow a Relay
 * Methods: `POST`
diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex
index dc01f46f3..9ec50bb90 100644
--- a/lib/pleroma/web/admin_api/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/admin_api_controller.ex
@@ -124,6 +124,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     |> json(%{error: "No such permission_group"})
   end
 
+  def set_activation_status(conn, %{"nickname" => nickname, "status" => status}) do
+    with {:ok, status} <- Ecto.Type.cast(:boolean, status),
+         %User{} = user <- User.get_by_nickname(nickname),
+         {:ok, _} <- User.deactivate(user, !status),
+         do: json_response(conn, :no_content, "")
+  end
+
   def relay_follow(conn, %{"relay_url" => target}) do
     with {:ok, _message} <- Relay.follow(target) do
       json(conn, target)
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 9a6cf2232..a4a382110 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -124,6 +124,8 @@ defmodule Pleroma.Web.Router do
     post("/permission_group/:nickname/:permission_group", AdminAPIController, :right_add)
     delete("/permission_group/:nickname/:permission_group", AdminAPIController, :right_delete)
 
+    put("/activation_status/:nickname", AdminAPIController, :set_activation_status)
+
     post("/relay", AdminAPIController, :relay_follow)
     delete("/relay", AdminAPIController, :relay_unfollow)
 
diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs
index a27c26f95..9fbaaba39 100644
--- a/test/web/admin_api/admin_api_controller_test.exs
+++ b/test/web/admin_api/admin_api_controller_test.exs
@@ -159,6 +159,54 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
   end
 
+  describe "PUT /api/pleroma/admin/activation_status" do
+    setup %{conn: conn} do
+      admin = insert(:user, info: %{is_admin: true})
+
+      conn =
+        conn
+        |> assign(:user, admin)
+        |> put_req_header("accept", "application/json")
+
+      %{conn: conn}
+    end
+
+    test "deactivates the user", %{conn: conn} do
+      user = insert(:user)
+
+      conn =
+        conn
+        |> put("/api/pleroma/admin/activation_status/#{user.nickname}", %{status: false})
+
+      user = Repo.get(User, user.id)
+      assert user.info.deactivated == true
+      assert json_response(conn, :no_content)
+    end
+
+    test "activates the user", %{conn: conn} do
+      user = insert(:user, info: %{deactivated: true})
+
+      conn =
+        conn
+        |> put("/api/pleroma/admin/activation_status/#{user.nickname}", %{status: true})
+
+      user = Repo.get(User, user.id)
+      assert user.info.deactivated == false
+      assert json_response(conn, :no_content)
+    end
+
+    test "returns 403 when requested by a non-admin", %{conn: conn} do
+      user = insert(:user)
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> put("/api/pleroma/admin/activation_status/#{user.nickname}", %{status: false})
+
+      assert json_response(conn, :forbidden)
+    end
+  end
+
   describe "POST /api/pleroma/admin/email_invite, with valid config" do
     setup do
       registrations_open = Pleroma.Config.get([:instance, :registrations_open])

From f41f017bbca232c2ab3cd41ad23ef95d5ad13e36 Mon Sep 17 00:00:00 2001
From: Ekaterina Vaartis <vaartis@cock.li>
Date: Wed, 5 Sep 2018 23:49:15 +0300
Subject: [PATCH 02/10] Implement muting, add it to the mastodon API

---
 lib/pleroma/user.ex                           | 24 +++++++++++++++++++
 lib/pleroma/web/activity_pub/activity_pub.ex  | 13 ++++++++++
 .../mastodon_api/mastodon_api_controller.ex   | 18 ++++++++++++++
 .../web/mastodon_api/views/account_view.ex    |  1 +
 lib/pleroma/web/router.ex                     |  4 ++--
 5 files changed, 58 insertions(+), 2 deletions(-)

diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 18bb56667..9a774e7b7 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -888,6 +888,28 @@ defmodule Pleroma.User do
     )
   end
 
+  def mute(muter, %User{ap_id: ap_id} = muted) do
+    if following?(muter, muter) do
+      unfollow(muter, muter)
+    end
+
+    mutes = muter.info["mutes"] || []
+    new_mutes = Enum.uniq([ap_id | mutes])
+    new_info = Map.put(muter.info, "mutes", new_mutes)
+
+    cs = User.info_changeset(muter, %{info: new_info})
+    update_and_set_cache(cs)
+  end
+
+  def unmute(user, %{ap_id: ap_id}) do
+    mutes = user.info["mutes"] || []
+    new_mutes = List.delete(mutes, ap_id)
+    new_info = Map.put(user.info, "mutes", new_mutes)
+
+    cs = User.info_changeset(user, %{info: new_info})
+    update_and_set_cache(cs)
+  end
+
   def block(blocker, %User{ap_id: ap_id} = blocked) do
     # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
     blocker =
@@ -930,6 +952,8 @@ defmodule Pleroma.User do
     update_and_set_cache(cng)
   end
 
+  def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info["mutes"] || [], ap_id)
+
   def blocks?(user, %{ap_id: ap_id}) do
     blocks = user.info.blocks
     domain_blocks = user.info.domain_blocks
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 8d3116839..36e5e23bf 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -576,6 +576,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_reblogs(query, _), do: query
 
+  defp restrict_muted(query, %{"muting_user" => %User{info: info}}) do
+    mutes = info["mutes"] || []
+
+    from(
+      activity in query,
+      where: fragment("not (? = ANY(?))", activity.actor, ^mutes),
+      where: fragment("not (?->'to' \\?| ?)", activity.data, ^mutes)
+    )
+  end
+
+  defp restrict_muted(query, _), do: query
+
   defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
     blocks = info.blocks || []
     domain_blocks = info.domain_blocks || []
@@ -629,6 +641,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     |> restrict_type(opts)
     |> restrict_favorited_by(opts)
     |> restrict_blocked(opts)
+    |> restrict_muted(opts)
     |> restrict_media(opts)
     |> restrict_visibility(opts)
     |> restrict_replies(opts)
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index e2715bd08..af16264ee 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -232,6 +232,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       params
       |> Map.put("type", ["Create", "Announce"])
       |> Map.put("blocking_user", user)
+      |> Map.put("muting_user", user)
       |> Map.put("user", user)
 
     activities =
@@ -254,6 +255,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       |> Map.put("type", ["Create", "Announce"])
       |> Map.put("local_only", local_only)
       |> Map.put("blocking_user", user)
+      |> Map.put("muting_user", user)
       |> ActivityPub.fetch_public_activities()
       |> Enum.reverse()
 
@@ -620,6 +622,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       |> Map.put("type", "Create")
       |> Map.put("local_only", local_only)
       |> Map.put("blocking_user", user)
+      |> Map.put("muting_user", user)
       |> Map.put("tag", tags)
       |> Map.put("tag_all", tag_all)
       |> Map.put("tag_reject", tag_reject)
@@ -763,6 +766,20 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     end
   end
 
+  def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
+    with %User{} = muted <- Repo.get(User, id),
+         {:ok, muter} <- User.mute(muter, muted) do
+      render(conn, AccountView, "relationship.json", %{user: muter, target: muted})
+    end
+  end
+
+  def unmute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
+    with %User{} = muted <- Repo.get(User, id),
+         {:ok, muter} <- User.unmute(muter, muted) do
+      render(conn, AccountView, "relationship.json", %{user: muter, target: muted})
+    end
+  end
+
   def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
     with %User{} = blocked <- Repo.get(User, id),
          {:ok, blocker} <- User.block(blocker, blocked),
@@ -1018,6 +1035,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
         params
         |> Map.put("type", "Create")
         |> Map.put("blocking_user", user)
+        |> Map.put("muting_user", user)
 
       # we must filter the following list for the user to avoid leaking statuses the user
       # does not actually have permission to see (for more info, peruse security issue #270).
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index 9df9f14b2..91b3e034f 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -47,6 +47,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
       following: User.following?(user, target),
       followed_by: User.following?(target, user),
       blocking: User.blocks?(user, target),
+      muting: User.mutes?(user, target),
       muting: false,
       muting_notifications: false,
       requested: requested,
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 9a6cf2232..fb7a8d448 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -166,8 +166,8 @@ defmodule Pleroma.Web.Router do
     post("/accounts/:id/unfollow", MastodonAPIController, :unfollow)
     post("/accounts/:id/block", MastodonAPIController, :block)
     post("/accounts/:id/unblock", MastodonAPIController, :unblock)
-    post("/accounts/:id/mute", MastodonAPIController, :relationship_noop)
-    post("/accounts/:id/unmute", MastodonAPIController, :relationship_noop)
+    post("/accounts/:id/mute", MastodonAPIController, :mute)
+    post("/accounts/:id/unmute", MastodonAPIController, :unmute)
     get("/accounts/:id/lists", MastodonAPIController, :account_lists)
 
     get("/follow_requests", MastodonAPIController, :follow_requests)

From da64ea4a5540bf21770de039e27a33b5ef9fb377 Mon Sep 17 00:00:00 2001
From: Ekaterina Vaartis <vaartis@cock.li>
Date: Sun, 2 Sep 2018 00:33:13 +0300
Subject: [PATCH 03/10] Implement mastodon mutes endpoint

Aparently i forgot to add it, it gets a list of muted users
---
 lib/pleroma/web/mastodon_api/mastodon_api_controller.ex | 9 +++++++++
 lib/pleroma/web/router.ex                               | 2 +-
 2 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index af16264ee..49b49be19 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -780,6 +780,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     end
   end
 
+  # TODO: Use proper query
+  def mutes(%{assigns: %{user: user}} = conn, _) do
+    with muted_users <- user.info["mutes"] || [],
+         accounts <- Enum.map(muted_users, fn ap_id -> User.get_cached_by_ap_id(ap_id) end) do
+      res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
+      json(conn, res)
+    end
+  end
+
   def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
     with %User{} = blocked <- Repo.get(User, id),
          {:ok, blocker} <- User.block(blocker, blocked),
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index fb7a8d448..1b62d02ea 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -178,7 +178,7 @@ defmodule Pleroma.Web.Router do
 
     get("/blocks", MastodonAPIController, :blocks)
 
-    get("/mutes", MastodonAPIController, :empty_array)
+    get("/mutes", MastodonAPIController, :mutes)
 
     get("/timelines/home", MastodonAPIController, :home_timeline)
 

From ad2cf4fd86b811a9d2a4c152bcaaa0f0b8e25341 Mon Sep 17 00:00:00 2001
From: Ekaterina Vaartis <vaartis@cock.li>
Date: Sun, 2 Sep 2018 00:34:15 +0300
Subject: [PATCH 04/10] Add test for mastodon muting endpoints

---
 .../mastodon_api_controller_test.exs          | 56 +++++++++++++------
 1 file changed, 38 insertions(+), 18 deletions(-)

diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs
index e43bc4508..e804ae203 100644
--- a/test/web/mastodon_api/mastodon_api_controller_test.exs
+++ b/test/web/mastodon_api/mastodon_api_controller_test.exs
@@ -1206,6 +1206,42 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     assert id == to_string(other_user.id)
   end
 
+  test "muting / unmuting a user", %{conn: conn} do
+    user = insert(:user)
+    other_user = insert(:user)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> post("/api/v1/accounts/#{other_user.id}/mute")
+
+    assert %{"id" => _id, "muting" => true} = json_response(conn, 200)
+
+    user = Repo.get(User, user.id)
+
+    conn =
+      build_conn()
+      |> assign(:user, user)
+      |> post("/api/v1/accounts/#{other_user.id}/unmute")
+
+    assert %{"id" => _id, "muting" => false} = json_response(conn, 200)
+  end
+
+  test "getting a list of mutes", %{conn: conn} do
+    user = insert(:user)
+    other_user = insert(:user)
+
+    {:ok, user} = User.mute(user, other_user)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> get("/api/v1/mutes")
+
+    other_user_id = to_string(other_user.id)
+    assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
+  end
+
   test "blocking / unblocking a user", %{conn: conn} do
     user = insert(:user)
     other_user = insert(:user)
@@ -1282,26 +1318,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     assert "even.worse.site" in domain_blocks
   end
 
-  test "unimplemented mute endpoints" do
-    user = insert(:user)
-    other_user = insert(:user)
-
-    ["mute", "unmute"]
-    |> Enum.each(fn endpoint ->
-      conn =
-        build_conn()
-        |> assign(:user, user)
-        |> post("/api/v1/accounts/#{other_user.id}/#{endpoint}")
-
-      assert %{"id" => id} = json_response(conn, 200)
-      assert id == to_string(other_user.id)
-    end)
-  end
-
-  test "unimplemented mutes, follow_requests, blocks, domain blocks" do
+  test "unimplemented follow_requests, blocks, domain blocks" do
     user = insert(:user)
 
-    ["blocks", "domain_blocks", "mutes", "follow_requests"]
+    ["blocks", "domain_blocks", "follow_requests"]
     |> Enum.each(fn endpoint ->
       conn =
         build_conn()

From 092b1b145335d530d7343116eb452e37cc112a9e Mon Sep 17 00:00:00 2001
From: Ekaterina Vaartis <vaartis@cock.li>
Date: Tue, 4 Sep 2018 00:17:25 +0300
Subject: [PATCH 05/10] Do not unfollow muted users

---
 lib/pleroma/user.ex | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 9a774e7b7..da25a91c8 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -889,10 +889,6 @@ defmodule Pleroma.User do
   end
 
   def mute(muter, %User{ap_id: ap_id} = muted) do
-    if following?(muter, muter) do
-      unfollow(muter, muter)
-    end
-
     mutes = muter.info["mutes"] || []
     new_mutes = Enum.uniq([ap_id | mutes])
     new_info = Map.put(muter.info, "mutes", new_mutes)

From 465b547c905b4faa26ec1cf5f0175c55430e8041 Mon Sep 17 00:00:00 2001
From: Ekaterina Vaartis <vaartis@cock.li>
Date: Tue, 4 Sep 2018 00:20:22 +0300
Subject: [PATCH 06/10] Remove unused "muted" parameter, use piping for
 mute/block fns

---
 lib/pleroma/user.ex | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index da25a91c8..cfb44ebaa 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -888,13 +888,13 @@ defmodule Pleroma.User do
     )
   end
 
-  def mute(muter, %User{ap_id: ap_id} = muted) do
+  def mute(muter, %User{ap_id: ap_id}) do
     mutes = muter.info["mutes"] || []
     new_mutes = Enum.uniq([ap_id | mutes])
     new_info = Map.put(muter.info, "mutes", new_mutes)
 
-    cs = User.info_changeset(muter, %{info: new_info})
-    update_and_set_cache(cs)
+    User.info_changeset(muter, %{info: new_info})
+    |> update_and_set_cache()
   end
 
   def unmute(user, %{ap_id: ap_id}) do
@@ -902,8 +902,8 @@ defmodule Pleroma.User do
     new_mutes = List.delete(mutes, ap_id)
     new_info = Map.put(user.info, "mutes", new_mutes)
 
-    cs = User.info_changeset(user, %{info: new_info})
-    update_and_set_cache(cs)
+    User.info_changeset(user, %{info: new_info})
+    |> update_and_set_cache()
   end
 
   def block(blocker, %User{ap_id: ap_id} = blocked) do

From 5a46d37af9fb1914d795cb90d28356efcd0790d5 Mon Sep 17 00:00:00 2001
From: Ekaterina Vaartis <vaartis@cock.li>
Date: Tue, 19 Feb 2019 23:09:16 +0300
Subject: [PATCH 07/10] Update the mute implementation to the current codebase

Make it part of the info thing (and do a migration to ensure it's there)
---
 lib/pleroma/user.ex                           | 33 ++++++++++++-------
 lib/pleroma/user/info.ex                      | 17 ++++++++++
 lib/pleroma/web/activity_pub/activity_pub.ex  |  2 +-
 .../mastodon_api/mastodon_api_controller.ex   | 24 ++++++++++----
 .../web/mastodon_api/views/account_view.ex    |  1 -
 .../20190219192317_create_user_mutes.exs      |  7 ++++
 6 files changed, 64 insertions(+), 20 deletions(-)
 create mode 100644 priv/repo/migrations/20190219192317_create_user_mutes.exs

diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index cfb44ebaa..35ba4ad99 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -889,21 +889,27 @@ defmodule Pleroma.User do
   end
 
   def mute(muter, %User{ap_id: ap_id}) do
-    mutes = muter.info["mutes"] || []
-    new_mutes = Enum.uniq([ap_id | mutes])
-    new_info = Map.put(muter.info, "mutes", new_mutes)
+    info_cng =
+      muter.info
+      |> User.Info.add_to_mutes(ap_id)
 
-    User.info_changeset(muter, %{info: new_info})
-    |> update_and_set_cache()
+    cng =
+      change(muter)
+      |> put_embed(:info, info_cng)
+
+    update_and_set_cache(cng)
   end
 
-  def unmute(user, %{ap_id: ap_id}) do
-    mutes = user.info["mutes"] || []
-    new_mutes = List.delete(mutes, ap_id)
-    new_info = Map.put(user.info, "mutes", new_mutes)
+  def unmute(muter, %{ap_id: ap_id}) do
+    info_cng =
+      muter.info
+      |> User.Info.remove_from_mutes(ap_id)
 
-    User.info_changeset(user, %{info: new_info})
-    |> update_and_set_cache()
+    cng =
+      change(muter)
+      |> put_embed(:info, info_cng)
+
+    update_and_set_cache(cng)
   end
 
   def block(blocker, %User{ap_id: ap_id} = blocked) do
@@ -948,7 +954,7 @@ defmodule Pleroma.User do
     update_and_set_cache(cng)
   end
 
-  def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info["mutes"] || [], ap_id)
+  def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id)
 
   def blocks?(user, %{ap_id: ap_id}) do
     blocks = user.info.blocks
@@ -961,6 +967,9 @@ defmodule Pleroma.User do
       end)
   end
 
+  def muted_users(user),
+    do: Repo.all(from(u in User, where: u.ap_id in ^user.info.mutes))
+
   def blocked_users(user),
     do: Repo.all(from(u in User, where: u.ap_id in ^user.info.blocks))
 
diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex
index 9099d7fbb..00a0f6df3 100644
--- a/lib/pleroma/user/info.ex
+++ b/lib/pleroma/user/info.ex
@@ -19,6 +19,7 @@ defmodule Pleroma.User.Info do
     field(:default_scope, :string, default: "public")
     field(:blocks, {:array, :string}, default: [])
     field(:domain_blocks, {:array, :string}, default: [])
+    field(:mutes, {:array, :string}, default: [])
     field(:deactivated, :boolean, default: false)
     field(:no_rich_text, :boolean, default: false)
     field(:ap_enabled, :boolean, default: false)
@@ -74,6 +75,14 @@ defmodule Pleroma.User.Info do
     |> validate_required([:follower_count])
   end
 
+  def set_mutes(info, mutes) do
+    params = %{mutes: mutes}
+
+    info
+    |> cast(params, [:mutes])
+    |> validate_required([:mutes])
+  end
+
   def set_blocks(info, blocks) do
     params = %{blocks: blocks}
 
@@ -82,6 +91,14 @@ defmodule Pleroma.User.Info do
     |> validate_required([:blocks])
   end
 
+  def add_to_mutes(info, muted) do
+    set_mutes(info, Enum.uniq([muted | info.mutes]))
+  end
+
+  def remove_from_mutes(info, muted) do
+    set_mutes(info, List.delete(info.mutes, muted))
+  end
+
   def add_to_block(info, blocked) do
     set_blocks(info, Enum.uniq([blocked | info.blocks]))
   end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 36e5e23bf..cb8a2139e 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -577,7 +577,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   defp restrict_reblogs(query, _), do: query
 
   defp restrict_muted(query, %{"muting_user" => %User{info: info}}) do
-    mutes = info["mutes"] || []
+    mutes = info.mutes
 
     from(
       activity in query,
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 49b49be19..3a343f3d8 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -769,22 +769,34 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
     with %User{} = muted <- Repo.get(User, id),
          {:ok, muter} <- User.mute(muter, muted) do
-      render(conn, AccountView, "relationship.json", %{user: muter, target: muted})
+      conn
+      |> put_view(AccountView)
+      |> render("relationship.json", %{user: muter, target: muted})
+    else
+      {:error, message} ->
+        conn
+        |> put_resp_content_type("application/json")
+        |> send_resp(403, Jason.encode!(%{"error" => message}))
     end
   end
 
   def unmute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
     with %User{} = muted <- Repo.get(User, id),
          {:ok, muter} <- User.unmute(muter, muted) do
-      render(conn, AccountView, "relationship.json", %{user: muter, target: muted})
+      conn
+      |> put_view(AccountView)
+      |> render("relationship.json", %{user: muter, target: muted})
+    else
+      {:error, message} ->
+        conn
+        |> put_resp_content_type("application/json")
+        |> send_resp(403, Jason.encode!(%{"error" => message}))
     end
   end
 
-  # TODO: Use proper query
   def mutes(%{assigns: %{user: user}} = conn, _) do
-    with muted_users <- user.info["mutes"] || [],
-         accounts <- Enum.map(muted_users, fn ap_id -> User.get_cached_by_ap_id(ap_id) end) do
-      res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
+    with muted_accounts <- User.muted_users(user) do
+      res = AccountView.render("accounts.json", users: muted_accounts, for: user, as: :user)
       json(conn, res)
     end
   end
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index 91b3e034f..8fdefdebd 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -48,7 +48,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
       followed_by: User.following?(target, user),
       blocking: User.blocks?(user, target),
       muting: User.mutes?(user, target),
-      muting: false,
       muting_notifications: false,
       requested: requested,
       domain_blocking: false,
diff --git a/priv/repo/migrations/20190219192317_create_user_mutes.exs b/priv/repo/migrations/20190219192317_create_user_mutes.exs
new file mode 100644
index 000000000..304074567
--- /dev/null
+++ b/priv/repo/migrations/20190219192317_create_user_mutes.exs
@@ -0,0 +1,7 @@
+defmodule Pleroma.Repo.Migrations.CreateUserMutes do
+  use Ecto.Migration
+
+  def change do
+    execute "UPDATE users SET info = jsonb_set(info, '{mutes}', '[]'::jsonb);"
+  end
+end

From 840d2c9f0bcc493703d12409bc64d2931b9ee3f5 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Wed, 20 Feb 2019 13:47:24 +0100
Subject: [PATCH 08/10] Remove superfluous migration.

---
 priv/repo/migrations/20190219192317_create_user_mutes.exs | 7 -------
 1 file changed, 7 deletions(-)
 delete mode 100644 priv/repo/migrations/20190219192317_create_user_mutes.exs

diff --git a/priv/repo/migrations/20190219192317_create_user_mutes.exs b/priv/repo/migrations/20190219192317_create_user_mutes.exs
deleted file mode 100644
index 304074567..000000000
--- a/priv/repo/migrations/20190219192317_create_user_mutes.exs
+++ /dev/null
@@ -1,7 +0,0 @@
-defmodule Pleroma.Repo.Migrations.CreateUserMutes do
-  use Ecto.Migration
-
-  def change do
-    execute "UPDATE users SET info = jsonb_set(info, '{mutes}', '[]'::jsonb);"
-  end
-end

From 9ae79bb71a2f0e9f88275f350126f1b1ba02c734 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Wed, 20 Feb 2019 13:47:44 +0100
Subject: [PATCH 09/10] Add test for muting functionality.

---
 test/web/activity_pub/activity_pub_test.exs | 42 +++++++++++++++++++++
 1 file changed, 42 insertions(+)

diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs
index a6f8b822a..33ed17434 100644
--- a/test/web/activity_pub/activity_pub_test.exs
+++ b/test/web/activity_pub/activity_pub_test.exs
@@ -277,6 +277,48 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     assert Enum.member?(activities, activity_one)
   end
 
+  test "doesn't return muted activities" do
+    activity_one = insert(:note_activity)
+    activity_two = insert(:note_activity)
+    activity_three = insert(:note_activity)
+    user = insert(:user)
+    booster = insert(:user)
+    {:ok, user} = User.mute(user, %User{ap_id: activity_one.data["actor"]})
+
+    activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
+
+    assert Enum.member?(activities, activity_two)
+    assert Enum.member?(activities, activity_three)
+    refute Enum.member?(activities, activity_one)
+
+    {:ok, user} = User.unmute(user, %User{ap_id: activity_one.data["actor"]})
+
+    activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
+
+    assert Enum.member?(activities, activity_two)
+    assert Enum.member?(activities, activity_three)
+    assert Enum.member?(activities, activity_one)
+
+    {:ok, user} = User.mute(user, %User{ap_id: activity_three.data["actor"]})
+    {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
+    %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
+    activity_three = Repo.get(Activity, activity_three.id)
+
+    activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
+
+    assert Enum.member?(activities, activity_two)
+    refute Enum.member?(activities, activity_three)
+    refute Enum.member?(activities, boost_activity)
+    assert Enum.member?(activities, activity_one)
+
+    activities = ActivityPub.fetch_activities([], %{"muting_user" => nil})
+
+    assert Enum.member?(activities, activity_two)
+    assert Enum.member?(activities, activity_three)
+    assert Enum.member?(activities, boost_activity)
+    assert Enum.member?(activities, activity_one)
+  end
+
   test "excludes reblogs on request" do
     user = insert(:user)
     {:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user})

From 4196d9af111893e186bfedd8a03994cd02cf87a2 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Wed, 20 Feb 2019 14:14:52 +0100
Subject: [PATCH 10/10] Add test for User.mutes and so on.

---
 test/user_test.exs | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/test/user_test.exs b/test/user_test.exs
index 92991d063..0b1c39ecf 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -594,6 +594,29 @@ defmodule Pleroma.UserTest do
     end
   end
 
+  describe "mutes" do
+    test "it mutes people" do
+      user = insert(:user)
+      muted_user = insert(:user)
+
+      refute User.mutes?(user, muted_user)
+
+      {:ok, user} = User.mute(user, muted_user)
+
+      assert User.mutes?(user, muted_user)
+    end
+
+    test "it unmutes users" do
+      user = insert(:user)
+      muted_user = insert(:user)
+
+      {:ok, user} = User.mute(user, muted_user)
+      {:ok, user} = User.unmute(user, muted_user)
+
+      refute User.mutes?(user, muted_user)
+    end
+  end
+
   describe "blocks" do
     test "it blocks people" do
       user = insert(:user)