forked from AkkomaGang/akkoma
Merge branch 'feature/blocks' into 'develop'
Feature/blocks See merge request pleroma/pleroma!5
This commit is contained in:
commit
fa1f11e8e9
12 changed files with 190 additions and 21 deletions
|
@ -46,9 +46,11 @@ def create_notifications(_), do: {:ok, []}
|
|||
|
||||
# TODO move to sql, too.
|
||||
def create_notification(%Activity{} = activity, %User{} = user) do
|
||||
notification = %Notification{user_id: user.id, activity_id: activity.id}
|
||||
{:ok, notification} = Repo.insert(notification)
|
||||
notification
|
||||
unless User.blocks?(user, %{ap_id: activity.data["actor"]}) do
|
||||
notification = %Notification{user_id: user.id, activity_id: activity.id}
|
||||
{:ok, notification} = Repo.insert(notification)
|
||||
notification
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -293,4 +293,28 @@ def search(query, resolve) do
|
|||
limit: 20
|
||||
Repo.all(q)
|
||||
end
|
||||
|
||||
def block(user, %{ap_id: ap_id}) do
|
||||
blocks = user.info["blocks"] || []
|
||||
new_blocks = Enum.uniq([ap_id | blocks])
|
||||
new_info = Map.put(user.info, "blocks", new_blocks)
|
||||
|
||||
cs = User.info_changeset(user, %{info: new_info})
|
||||
Repo.update(cs)
|
||||
end
|
||||
|
||||
def unblock(user, %{ap_id: ap_id}) do
|
||||
blocks = user.info["blocks"] || []
|
||||
new_blocks = List.delete(blocks, ap_id)
|
||||
new_info = Map.put(user.info, "blocks", new_blocks)
|
||||
|
||||
cs = User.info_changeset(user, %{info: new_info})
|
||||
Repo.update(cs)
|
||||
end
|
||||
|
||||
def blocks?(user, %{ap_id: ap_id}) do
|
||||
blocks = user.info["blocks"] || []
|
||||
Enum.member?(blocks, ap_id)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -93,10 +93,11 @@ def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ tru
|
|||
end
|
||||
end
|
||||
|
||||
def fetch_activities_for_context(context) do
|
||||
def fetch_activities_for_context(context, opts \\ %{}) do
|
||||
query = from activity in Activity,
|
||||
where: fragment("?->>'type' = ? and ?->>'context' = ?", activity.data, "Create", activity.data, ^context),
|
||||
order_by: [desc: :id]
|
||||
query = restrict_blocked(query, opts)
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
|
@ -163,6 +164,13 @@ defp restrict_recent(query, _) do
|
|||
where: activity.id > ^since
|
||||
end
|
||||
|
||||
defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
|
||||
blocks = info["blocks"] || []
|
||||
from activity in query,
|
||||
where: fragment("not (?->>'actor' = ANY(?))", activity.data, ^blocks)
|
||||
end
|
||||
defp restrict_blocked(query, _), do: query
|
||||
|
||||
def fetch_activities(recipients, opts \\ %{}) do
|
||||
base_query = from activity in Activity,
|
||||
limit: 20,
|
||||
|
@ -178,6 +186,7 @@ def fetch_activities(recipients, opts \\ %{}) do
|
|||
|> restrict_type(opts)
|
||||
|> restrict_favorited_by(opts)
|
||||
|> restrict_recent(opts)
|
||||
|> restrict_blocked(opts)
|
||||
|> Repo.all
|
||||
|> Enum.reverse
|
||||
end
|
||||
|
|
|
@ -79,6 +79,7 @@ defp add_link_headers(conn, method, activities) do
|
|||
def home_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||
params = params
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("blocking_user", user)
|
||||
|
||||
activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
|
||||
|> Enum.reverse
|
||||
|
@ -92,6 +93,7 @@ def public_timeline(%{assigns: %{user: user}} = conn, params) do
|
|||
params = params
|
||||
|> Map.put("type", ["Create", "Announce"])
|
||||
|> Map.put("local_only", !!params["local"])
|
||||
|> Map.put("blocking_user", user)
|
||||
|
||||
activities = ActivityPub.fetch_public_activities(params)
|
||||
|> Enum.reverse
|
||||
|
@ -123,7 +125,7 @@ def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
|||
|
||||
def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with %Activity{} = activity <- Repo.get(Activity, id),
|
||||
activities <- ActivityPub.fetch_activities_for_context(activity.data["object"]["context"]),
|
||||
activities <- ActivityPub.fetch_activities_for_context(activity.data["object"]["context"], %{"blocking_user" => user}),
|
||||
activities <- activities |> Enum.filter(fn (%{id: aid}) -> to_string(aid) != to_string(id) end),
|
||||
grouped_activities <- Enum.group_by(activities, fn (%{id: id}) -> id < activity.id end) do
|
||||
result = %{
|
||||
|
@ -246,6 +248,7 @@ def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do
|
|||
params = params
|
||||
|> Map.put("type", "Create")
|
||||
|> Map.put("local_only", !!params["local"])
|
||||
|> Map.put("blocking_user", user)
|
||||
|
||||
activities = ActivityPub.fetch_public_activities(params)
|
||||
|> Enum.reverse
|
||||
|
@ -308,6 +311,39 @@ def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
|||
end
|
||||
end
|
||||
|
||||
def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||
with %User{} = blocked <- Repo.get(User, id),
|
||||
{:ok, blocker} <- User.block(blocker, blocked) do
|
||||
render conn, AccountView, "relationship.json", %{user: blocker, target: blocked}
|
||||
else
|
||||
{:error, message} = err ->
|
||||
conn
|
||||
|> put_resp_content_type("application/json")
|
||||
|> send_resp(403, Poison.encode!(%{"error" => message}))
|
||||
end
|
||||
end
|
||||
|
||||
def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||
with %User{} = blocked <- Repo.get(User, id),
|
||||
{:ok, blocker} <- User.unblock(blocker, blocked) do
|
||||
render conn, AccountView, "relationship.json", %{user: blocker, target: blocked}
|
||||
else
|
||||
{:error, message} = err ->
|
||||
conn
|
||||
|> put_resp_content_type("application/json")
|
||||
|> send_resp(403, Poison.encode!(%{"error" => message}))
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Use proper query
|
||||
def blocks(%{assigns: %{user: user}} = conn, _) do
|
||||
with blocked_users <- user.info["blocks"] || [],
|
||||
accounts <- Enum.map(blocked_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 search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
||||
accounts = User.search(query, params["resolve"] == "true")
|
||||
|
||||
|
@ -338,6 +374,7 @@ def favourites(%{assigns: %{user: user}} = conn, params) do
|
|||
params = conn
|
||||
|> Map.put("type", "Create")
|
||||
|> Map.put("favorited_by", user.ap_id)
|
||||
|> Map.put("blocking_user", user)
|
||||
|
||||
activities = ActivityPub.fetch_activities([], params)
|
||||
|> Enum.reverse
|
||||
|
|
|
@ -55,7 +55,7 @@ def render("relationship.json", %{user: user, target: target}) do
|
|||
id: target.id,
|
||||
following: User.following?(user, target),
|
||||
followed_by: User.following?(target, user),
|
||||
blocking: false,
|
||||
blocking: User.blocks?(user, target),
|
||||
muting: false,
|
||||
requested: false,
|
||||
domain_blocking: false
|
||||
|
|
|
@ -58,14 +58,15 @@ def user_fetcher(username) do
|
|||
get "/accounts/search", MastodonAPIController, :account_search
|
||||
post "/accounts/:id/follow", MastodonAPIController, :follow
|
||||
post "/accounts/:id/unfollow", MastodonAPIController, :unfollow
|
||||
post "/accounts/:id/block", MastodonAPIController, :relationship_noop
|
||||
post "/accounts/:id/unblock", MastodonAPIController, :relationship_noop
|
||||
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 "/follows", MastodonAPIController, :follow
|
||||
|
||||
get "/blocks", MastodonAPIController, :empty_array
|
||||
get "/blocks", MastodonAPIController, :blocks
|
||||
|
||||
get "/domain_blocks", MastodonAPIController, :empty_array
|
||||
get "/follow_requests", MastodonAPIController, :empty_array
|
||||
get "/mutes", MastodonAPIController, :empty_array
|
||||
|
|
|
@ -14,17 +14,20 @@ def create_status(%User{} = user, %{"status" => status} = data) do
|
|||
end
|
||||
|
||||
def fetch_friend_statuses(user, opts \\ %{}) do
|
||||
opts = Map.put(opts, "blocking_user", user)
|
||||
ActivityPub.fetch_activities([user.ap_id | user.following], opts)
|
||||
|> activities_to_statuses(%{for: user})
|
||||
end
|
||||
|
||||
def fetch_public_statuses(user, opts \\ %{}) do
|
||||
opts = Map.put(opts, "local_only", true)
|
||||
opts = Map.put(opts, "blocking_user", user)
|
||||
ActivityPub.fetch_public_activities(opts)
|
||||
|> activities_to_statuses(%{for: user})
|
||||
end
|
||||
|
||||
def fetch_public_and_external_statuses(user, opts \\ %{}) do
|
||||
opts = Map.put(opts, "blocking_user", user)
|
||||
ActivityPub.fetch_public_activities(opts)
|
||||
|> activities_to_statuses(%{for: user})
|
||||
end
|
||||
|
@ -41,7 +44,7 @@ def fetch_mentions(user, opts \\ %{}) do
|
|||
|
||||
def fetch_conversation(user, id) do
|
||||
with context when is_binary(context) <- conversation_id_to_context(id),
|
||||
activities <- ActivityPub.fetch_activities_for_context(context),
|
||||
activities <- ActivityPub.fetch_activities_for_context(context, %{"blocking_user" => user}),
|
||||
statuses <- activities |> activities_to_statuses(%{for: user})
|
||||
do
|
||||
statuses
|
||||
|
|
|
@ -20,4 +20,15 @@ test "notifies someone when they are directly addressed" do
|
|||
assert other_notification.activity_id == activity.id
|
||||
end
|
||||
end
|
||||
|
||||
describe "create_notification" do
|
||||
test "it doesn't create a notification for user if the user blocks the activity author" do
|
||||
activity = insert(:note_activity)
|
||||
author = User.get_by_ap_id(activity.data["actor"])
|
||||
user = insert(:user)
|
||||
{:ok, user} = User.block(user, author)
|
||||
|
||||
assert nil == Notification.create_notification(activity, user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,11 +9,6 @@ defmodule Pleroma.UserTest do
|
|||
import Ecto.Query
|
||||
|
||||
test "ap_id returns the activity pub id for the user" do
|
||||
host =
|
||||
Application.get_env(:pleroma, Pleroma.Web.Endpoint)
|
||||
|> Keyword.fetch!(:url)
|
||||
|> Keyword.fetch!(:host)
|
||||
|
||||
user = UserBuilder.build
|
||||
|
||||
expected_ap_id = "#{Pleroma.Web.base_url}/users/#{user.nickname}"
|
||||
|
@ -213,7 +208,9 @@ test "gets all followers for a given user" do
|
|||
|
||||
{:ok, res} = User.get_followers(user)
|
||||
|
||||
assert res == [follower_one, follower_two]
|
||||
assert Enum.member?(res, follower_one)
|
||||
assert Enum.member?(res, follower_two)
|
||||
refute Enum.member?(res, not_follower)
|
||||
end
|
||||
|
||||
test "gets all friends (followed users) for a given user" do
|
||||
|
@ -229,7 +226,9 @@ test "gets all friends (followed users) for a given user" do
|
|||
|
||||
followed_one = User.get_by_ap_id(followed_one.ap_id)
|
||||
followed_two = User.get_by_ap_id(followed_two.ap_id)
|
||||
assert res == [followed_one, followed_two]
|
||||
assert Enum.member?(res, followed_one)
|
||||
assert Enum.member?(res, followed_two)
|
||||
refute Enum.member?(res, not_followed)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -274,5 +273,28 @@ test "it sets the info->follower_count property" do
|
|||
assert user.info["follower_count"] == 1
|
||||
end
|
||||
end
|
||||
|
||||
describe "blocks" do
|
||||
test "it blocks people" do
|
||||
user = insert(:user)
|
||||
blocked_user = insert(:user)
|
||||
|
||||
refute User.blocks?(user, blocked_user)
|
||||
|
||||
{:ok, user} = User.block(user, blocked_user)
|
||||
|
||||
assert User.blocks?(user, blocked_user)
|
||||
end
|
||||
|
||||
test "it unblocks users" do
|
||||
user = insert(:user)
|
||||
blocked_user = insert(:user)
|
||||
|
||||
{:ok, user} = User.block(user, blocked_user)
|
||||
{:ok, user} = User.unblock(user, blocked_user)
|
||||
|
||||
refute User.blocks?(user, blocked_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -73,13 +73,40 @@ test "retrieves activities that have a given context" do
|
|||
{:ok, activity_two} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})
|
||||
{:ok, _activity_three} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
|
||||
{:ok, _activity_four} = ActivityBuilder.insert(%{"type" => "Announce", "context" => "2hu"})
|
||||
activity_five = insert(:note_activity)
|
||||
user = insert(:user)
|
||||
|
||||
activities = ActivityPub.fetch_activities_for_context("2hu")
|
||||
{:ok, user} = User.block(user, %{ap_id: activity_five.data["actor"]})
|
||||
|
||||
activities = ActivityPub.fetch_activities_for_context("2hu", %{"blocking_user" => user})
|
||||
assert activities == [activity_two, activity]
|
||||
end
|
||||
end
|
||||
|
||||
test "doesn't return blocked activities" do
|
||||
activity_one = insert(:note_activity)
|
||||
activity_two = insert(:note_activity)
|
||||
user = insert(:user)
|
||||
{:ok, user} = User.block(user, %{ap_id: activity_one.data["actor"]})
|
||||
|
||||
activities = ActivityPub.fetch_activities([], %{"blocking_user" => user})
|
||||
|
||||
assert Enum.member?(activities, activity_two)
|
||||
refute Enum.member?(activities, activity_one)
|
||||
|
||||
{:ok, user} = User.unblock(user, %{ap_id: activity_one.data["actor"]})
|
||||
|
||||
activities = ActivityPub.fetch_activities([], %{"blocking_user" => user})
|
||||
|
||||
assert Enum.member?(activities, activity_two)
|
||||
assert Enum.member?(activities, activity_one)
|
||||
|
||||
activities = ActivityPub.fetch_activities([], %{"blocking_user" => nil})
|
||||
|
||||
assert Enum.member?(activities, activity_two)
|
||||
assert Enum.member?(activities, activity_one)
|
||||
end
|
||||
|
||||
describe "public fetch activities" do
|
||||
test "retrieves public activities" do
|
||||
%{public: public} = ActivityBuilder.public_and_non_public
|
||||
|
|
|
@ -51,12 +51,13 @@ test "represent a relationship" do
|
|||
other_user = insert(:user)
|
||||
|
||||
{:ok, user} = User.follow(user, other_user)
|
||||
{:ok, user} = User.block(user, other_user)
|
||||
|
||||
expected = %{
|
||||
id: other_user.id,
|
||||
following: true,
|
||||
followed_by: false,
|
||||
blocking: false,
|
||||
blocking: true,
|
||||
muting: false,
|
||||
requested: false,
|
||||
domain_blocking: false
|
||||
|
|
|
@ -291,11 +291,43 @@ test "following / unfollowing a user", %{conn: conn} do
|
|||
assert id == other_user.id
|
||||
end
|
||||
|
||||
test "unimplemented block/mute endpoints" do
|
||||
test "blocking / unblocking a user", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
["block", "unblock", "mute", "unmute"]
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/accounts/#{other_user.id}/block")
|
||||
|
||||
assert %{"id" => id, "blocking" => true} = json_response(conn, 200)
|
||||
|
||||
user = Repo.get(User, user.id)
|
||||
conn = build_conn()
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/accounts/#{other_user.id}/unblock")
|
||||
|
||||
assert %{"id" => id, "blocking" => false} = json_response(conn, 200)
|
||||
end
|
||||
|
||||
test "getting a list of blocks", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, user} = User.block(user, other_user)
|
||||
|
||||
conn = conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/blocks")
|
||||
|
||||
other_user_id = other_user.id
|
||||
assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
|
||||
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)
|
||||
|
|
Loading…
Reference in a new issue