aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md2
-rw-r--r--config/config.exs3
-rw-r--r--docs/API/differences_in_mastoapi_responses.md1
-rw-r--r--lib/pleroma/conversation.ex2
-rw-r--r--lib/pleroma/conversation/participation.ex17
-rw-r--r--lib/pleroma/user.ex56
-rw-r--r--lib/pleroma/user/info.ex1
-rw-r--r--lib/pleroma/web/activity_pub/utils.ex12
-rw-r--r--lib/pleroma/web/mastodon_api/views/account_view.ex11
-rw-r--r--test/conversation/participation_test.exs4
-rw-r--r--test/web/activity_pub/activity_pub_test.exs5
-rw-r--r--test/web/activity_pub/utils_test.exs6
-rw-r--r--test/web/mastodon_api/controllers/conversation_controller_test.exs101
-rw-r--r--test/web/mastodon_api/views/account_view_test.exs21
-rw-r--r--test/web/pleroma_api/controllers/pleroma_api_controller_test.exs2
15 files changed, 225 insertions, 19 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4a904a3f1..db505591b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Pleroma API: `GET /api/v1/pleroma/accounts/:id/scrobbles` to get a list of recently scrobbled items
- Pleroma API: `POST /api/v1/pleroma/scrobble` to scrobble a media item
- Mastodon API: Add `upload_limit`, `avatar_upload_limit`, `background_upload_limit`, and `banner_upload_limit` to `/api/v1/instance`
+- Mastodon API: Add `pleroma.unread_conversation_count` to the Account entity
### Changed
- **Breaking:** Elixir >=1.8 is now required (was >= 1.7)
@@ -25,6 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Fixed
- Mastodon API: Fix private and direct statuses not being filtered out from the public timeline for an authenticated user (`GET /api/v1/timelines/public`)
- Mastodon API: Inability to get some local users by nickname in `/api/v1/accounts/:id_or_nickname`
+- Added `:instance, extended_nickname_format` setting to the default config
## [1.1.0] - 2019-??-??
### Security
diff --git a/config/config.exs b/config/config.exs
index 36bea19a0..ddbfb246a 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -279,7 +279,8 @@ config :pleroma, :instance,
max_remote_account_fields: 20,
account_field_name_length: 512,
account_field_value_length: 2048,
- external_user_synchronization: true
+ external_user_synchronization: true,
+ extended_nickname_format: false
config :pleroma, :markup,
# XXX - unfortunately, inline images must be enabled by default right now, because
diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md
index d007a69c3..21b297529 100644
--- a/docs/API/differences_in_mastoapi_responses.md
+++ b/docs/API/differences_in_mastoapi_responses.md
@@ -56,6 +56,7 @@ Has these additional fields under the `pleroma` object:
- `settings_store`: A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`
- `chat_token`: The token needed for Pleroma chat. Only returned in `verify_credentials`
- `deactivated`: boolean, true when the user is deactivated
+- `unread_conversation_count`: The count of unread conversations. Only returned to the account owner.
### Source
diff --git a/lib/pleroma/conversation.ex b/lib/pleroma/conversation.ex
index be5821ad7..098016af2 100644
--- a/lib/pleroma/conversation.ex
+++ b/lib/pleroma/conversation.ex
@@ -67,6 +67,8 @@ defmodule Pleroma.Conversation do
participations =
Enum.map(users, fn user ->
+ User.increment_unread_conversation_count(conversation, user)
+
{:ok, participation} =
Participation.create_for_user_and_conversation(user, conversation, opts)
diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex
index e946f6de2..ab81f3217 100644
--- a/lib/pleroma/conversation/participation.ex
+++ b/lib/pleroma/conversation/participation.ex
@@ -52,6 +52,15 @@ defmodule Pleroma.Conversation.Participation do
participation
|> read_cng(%{read: true})
|> Repo.update()
+ |> case do
+ {:ok, participation} ->
+ participation = Repo.preload(participation, :user)
+ User.set_unread_conversation_count(participation.user)
+ {:ok, participation}
+
+ error ->
+ error
+ end
end
def mark_as_unread(participation) do
@@ -135,4 +144,12 @@ defmodule Pleroma.Conversation.Participation do
{:ok, Repo.preload(participation, :recipients, force: true)}
end
+
+ def unread_conversation_count_for_user(user) do
+ from(p in __MODULE__,
+ where: p.user_id == ^user.id,
+ where: not p.read,
+ select: %{count: count(p.id)}
+ )
+ end
end
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index c2f8fa0d7..494a67f22 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -11,6 +11,7 @@ defmodule Pleroma.User do
alias Comeonin.Pbkdf2
alias Ecto.Multi
alias Pleroma.Activity
+ alias Pleroma.Conversation.Participation
alias Pleroma.Delivery
alias Pleroma.Keys
alias Pleroma.Notification
@@ -842,6 +843,61 @@ defmodule Pleroma.User do
def maybe_update_following_count(user), do: user
+ def set_unread_conversation_count(%User{local: true} = user) do
+ unread_query = Participation.unread_conversation_count_for_user(user)
+
+ User
+ |> where([u], u.id == ^user.id)
+ |> join(:inner, [u], p in subquery(unread_query))
+ |> update([u, p],
+ set: [
+ info:
+ fragment(
+ "jsonb_set(?, '{unread_conversation_count}', ?::varchar::jsonb, true)",
+ u.info,
+ p.count
+ )
+ ]
+ )
+ |> select([u], u)
+ |> Repo.update_all([])
+ |> case do
+ {1, [%{info: %User.Info{}} = user]} -> set_cache(user)
+ _ -> {:error, user}
+ end
+ end
+
+ def set_unread_conversation_count(_), do: :noop
+
+ def increment_unread_conversation_count(conversation, %User{local: true} = user) do
+ unread_query =
+ Participation.unread_conversation_count_for_user(user)
+ |> where([p], p.conversation_id == ^conversation.id)
+
+ User
+ |> join(:inner, [u], p in subquery(unread_query))
+ |> update([u, p],
+ set: [
+ info:
+ fragment(
+ "jsonb_set(?, '{unread_conversation_count}', ((?->>'unread_conversation_count')::int + 1)::varchar::jsonb, true)",
+ u.info,
+ u.info
+ )
+ ]
+ )
+ |> where([u], u.id == ^user.id)
+ |> where([u, p], p.count == 0)
+ |> select([u], u)
+ |> Repo.update_all([])
+ |> case do
+ {1, [%{info: %User.Info{}} = user]} -> set_cache(user)
+ _ -> {:error, user}
+ end
+ end
+
+ def increment_unread_conversation_count(_, _), do: :noop
+
def remove_duplicated_following(%User{following: following} = user) do
uniq_following = Enum.uniq(following)
diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex
index ebd4ddebf..4b5b43d7f 100644
--- a/lib/pleroma/user/info.ex
+++ b/lib/pleroma/user/info.ex
@@ -47,6 +47,7 @@ defmodule Pleroma.User.Info do
field(:hide_followers, :boolean, default: false)
field(:hide_follows, :boolean, default: false)
field(:hide_favorites, :boolean, default: true)
+ field(:unread_conversation_count, :integer, default: 0)
field(:pinned_activities, {:array, :string}, default: [])
field(:email_notifications, :map, default: %{"digest" => false})
field(:mascot, :map, default: nil)
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index 0828591ee..ac5550671 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -461,14 +461,16 @@ defmodule Pleroma.Web.ActivityPub.Utils do
"""
def make_unannounce_data(
%User{ap_id: ap_id} = user,
- %Activity{data: %{"context" => context}} = activity,
+ %Activity{data: %{"context" => context, "object" => object}} = activity,
activity_id
) do
+ object = Object.normalize(object)
+
%{
"type" => "Undo",
"actor" => ap_id,
"object" => activity.data,
- "to" => [user.follower_address, activity.data["actor"]],
+ "to" => [user.follower_address, object.data["actor"]],
"cc" => [Pleroma.Constants.as_public()],
"context" => context
}
@@ -477,14 +479,16 @@ defmodule Pleroma.Web.ActivityPub.Utils do
def make_unlike_data(
%User{ap_id: ap_id} = user,
- %Activity{data: %{"context" => context}} = activity,
+ %Activity{data: %{"context" => context, "object" => object}} = activity,
activity_id
) do
+ object = Object.normalize(object)
+
%{
"type" => "Undo",
"actor" => ap_id,
"object" => activity.data,
- "to" => [user.follower_address, activity.data["actor"]],
+ "to" => [user.follower_address, object.data["actor"]],
"cc" => [Pleroma.Constants.as_public()],
"context" => context
}
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index 99169ef95..2d4976891 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -167,6 +167,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|> maybe_put_chat_token(user, opts[:for], opts)
|> maybe_put_activation_status(user, opts[:for])
|> maybe_put_follow_requests_count(user, opts[:for])
+ |> maybe_put_unread_conversation_count(user, opts[:for])
end
defp username_from_nickname(string) when is_binary(string) do
@@ -248,6 +249,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
defp maybe_put_activation_status(data, _, _), do: data
+ defp maybe_put_unread_conversation_count(data, %User{id: user_id} = user, %User{id: user_id}) do
+ data
+ |> Kernel.put_in(
+ [:pleroma, :unread_conversation_count],
+ user.info.unread_conversation_count
+ )
+ end
+
+ defp maybe_put_unread_conversation_count(data, _, _), do: data
+
defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
defp image_url(_), do: nil
end
diff --git a/test/conversation/participation_test.exs b/test/conversation/participation_test.exs
index a27167d42..f430bdf75 100644
--- a/test/conversation/participation_test.exs
+++ b/test/conversation/participation_test.exs
@@ -6,6 +6,7 @@ defmodule Pleroma.Conversation.ParticipationTest do
use Pleroma.DataCase
import Pleroma.Factory
alias Pleroma.Conversation.Participation
+ alias Pleroma.User
alias Pleroma.Web.CommonAPI
test "getting a participation will also preload things" do
@@ -30,6 +31,8 @@ defmodule Pleroma.Conversation.ParticipationTest do
{:ok, activity} =
CommonAPI.post(user, %{"status" => "Hey @#{other_user.nickname}.", "visibility" => "direct"})
+ user = User.get_cached_by_id(user.id)
+ other_user = User.get_cached_by_id(user.id)
[participation] = Participation.for_user(user)
participation = Pleroma.Repo.preload(participation, :recipients)
@@ -155,6 +158,7 @@ defmodule Pleroma.Conversation.ParticipationTest do
[participation] = Participation.for_user_with_last_activity_id(user)
participation = Repo.preload(participation, :recipients)
+ user = User.get_cached_by_id(user.id)
assert participation.recipients |> length() == 1
assert user in participation.recipients
diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs
index f29497847..c9f2a92e7 100644
--- a/test/web/activity_pub/activity_pub_test.exs
+++ b/test/web/activity_pub/activity_pub_test.exs
@@ -811,10 +811,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
{:ok, like_activity, object} = ActivityPub.like(user, object)
assert object.data["like_count"] == 1
- {:ok, _, _, object} = ActivityPub.unlike(user, object)
+ {:ok, unlike_activity, _, object} = ActivityPub.unlike(user, object)
assert object.data["like_count"] == 0
assert Activity.get_by_id(like_activity.id) == nil
+ assert note_activity.actor in unlike_activity.recipients
end
end
@@ -890,7 +891,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert unannounce_activity.data["to"] == [
User.ap_followers(user),
- announce_activity.data["actor"]
+ object.data["actor"]
]
assert unannounce_activity.data["type"] == "Undo"
diff --git a/test/web/activity_pub/utils_test.exs b/test/web/activity_pub/utils_test.exs
index b1c1d6f71..c57ea7eb9 100644
--- a/test/web/activity_pub/utils_test.exs
+++ b/test/web/activity_pub/utils_test.exs
@@ -106,11 +106,13 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
user = insert(:user)
like_activity = insert(:like_activity, data_attrs: %{"context" => "test context"})
+ object = Object.normalize(like_activity.data["object"])
+
assert Utils.make_unlike_data(user, like_activity, nil) == %{
"type" => "Undo",
"actor" => user.ap_id,
"object" => like_activity.data,
- "to" => [user.follower_address, like_activity.data["actor"]],
+ "to" => [user.follower_address, object.data["actor"]],
"cc" => [Pleroma.Constants.as_public()],
"context" => like_activity.data["context"]
}
@@ -119,7 +121,7 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
"type" => "Undo",
"actor" => user.ap_id,
"object" => like_activity.data,
- "to" => [user.follower_address, like_activity.data["actor"]],
+ "to" => [user.follower_address, object.data["actor"]],
"cc" => [Pleroma.Constants.as_public()],
"context" => like_activity.data["context"],
"id" => "9mJEZK0tky1w2xD2vY"
diff --git a/test/web/mastodon_api/controllers/conversation_controller_test.exs b/test/web/mastodon_api/controllers/conversation_controller_test.exs
index 7117fc76a..a308a7620 100644
--- a/test/web/mastodon_api/controllers/conversation_controller_test.exs
+++ b/test/web/mastodon_api/controllers/conversation_controller_test.exs
@@ -10,19 +10,23 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do
import Pleroma.Factory
- test "Conversations", %{conn: conn} do
+ test "returns a list of conversations", %{conn: conn} do
user_one = insert(:user)
user_two = insert(:user)
user_three = insert(:user)
{:ok, user_two} = User.follow(user_two, user_one)
+ assert User.get_cached_by_id(user_two.id).info.unread_conversation_count == 0
+
{:ok, direct} =
CommonAPI.post(user_one, %{
"status" => "Hi @#{user_two.nickname}, @#{user_three.nickname}!",
"visibility" => "direct"
})
+ assert User.get_cached_by_id(user_two.id).info.unread_conversation_count == 1
+
{:ok, _follower_only} =
CommonAPI.post(user_one, %{
"status" => "Hi @#{user_two.nickname}!",
@@ -52,23 +56,100 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do
assert is_binary(res_id)
assert unread == true
assert res_last_status["id"] == direct.id
+ assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 1
+ end
- # Apparently undocumented API endpoint
- res_conn =
+ test "updates the last_status on reply", %{conn: conn} do
+ user_one = insert(:user)
+ user_two = insert(:user)
+
+ {:ok, direct} =
+ CommonAPI.post(user_one, %{
+ "status" => "Hi @#{user_two.nickname}",
+ "visibility" => "direct"
+ })
+
+ {:ok, direct_reply} =
+ CommonAPI.post(user_two, %{
+ "status" => "reply",
+ "visibility" => "direct",
+ "in_reply_to_status_id" => direct.id
+ })
+
+ [%{"last_status" => res_last_status}] =
conn
|> assign(:user, user_one)
- |> post("/api/v1/conversations/#{res_id}/read")
+ |> get("/api/v1/conversations")
+ |> json_response(200)
- assert response = json_response(res_conn, 200)
- assert length(response["accounts"]) == 2
- assert response["last_status"]["id"] == direct.id
- assert response["unread"] == false
+ assert res_last_status["id"] == direct_reply.id
+ end
+
+ test "the user marks a conversation as read", %{conn: conn} do
+ user_one = insert(:user)
+ user_two = insert(:user)
+
+ {:ok, direct} =
+ CommonAPI.post(user_one, %{
+ "status" => "Hi @#{user_two.nickname}",
+ "visibility" => "direct"
+ })
+
+ [%{"id" => direct_conversation_id, "unread" => true}] =
+ conn
+ |> assign(:user, user_one)
+ |> get("/api/v1/conversations")
+ |> json_response(200)
+
+ %{"unread" => false} =
+ conn
+ |> assign(:user, user_one)
+ |> post("/api/v1/conversations/#{direct_conversation_id}/read")
+ |> json_response(200)
+
+ assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 0
+
+ # The conversation is marked as unread on reply
+ {:ok, _} =
+ CommonAPI.post(user_two, %{
+ "status" => "reply",
+ "visibility" => "direct",
+ "in_reply_to_status_id" => direct.id
+ })
+
+ [%{"unread" => true}] =
+ conn
+ |> assign(:user, user_one)
+ |> get("/api/v1/conversations")
+ |> json_response(200)
+
+ assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 1
+
+ # A reply doesn't increment the user's unread_conversation_count if the conversation is unread
+ {:ok, _} =
+ CommonAPI.post(user_two, %{
+ "status" => "reply",
+ "visibility" => "direct",
+ "in_reply_to_status_id" => direct.id
+ })
+
+ assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 1
+ end
+
+ test "(vanilla) Mastodon frontend behaviour", %{conn: conn} do
+ user_one = insert(:user)
+ user_two = insert(:user)
+
+ {:ok, direct} =
+ CommonAPI.post(user_one, %{
+ "status" => "Hi @#{user_two.nickname}!",
+ "visibility" => "direct"
+ })
- # (vanilla) Mastodon frontend behaviour
res_conn =
conn
|> assign(:user, user_one)
- |> get("/api/v1/statuses/#{res_last_status["id"]}/context")
+ |> get("/api/v1/statuses/#{direct.id}/context")
assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200)
end
diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs
index 62b2ab7e3..b7a4938a6 100644
--- a/test/web/mastodon_api/views/account_view_test.exs
+++ b/test/web/mastodon_api/views/account_view_test.exs
@@ -418,6 +418,27 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
following_count: 1
} = AccountView.render("show.json", %{user: user, for: user})
end
+
+ test "shows unread_conversation_count only to the account owner" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, _activity} =
+ CommonAPI.post(user, %{
+ "status" => "Hey @#{other_user.nickname}.",
+ "visibility" => "direct"
+ })
+
+ user = User.get_cached_by_ap_id(user.ap_id)
+
+ assert AccountView.render("show.json", %{user: user, for: other_user})[:pleroma][
+ :unread_conversation_count
+ ] == nil
+
+ assert AccountView.render("show.json", %{user: user, for: user})[:pleroma][
+ :unread_conversation_count
+ ] == 1
+ end
end
describe "follow requests counter" do
diff --git a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs
index 7eaeda4a0..8a6528cbb 100644
--- a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs
+++ b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
alias Pleroma.Conversation.Participation
alias Pleroma.Notification
alias Pleroma.Repo
+ alias Pleroma.User
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
@@ -73,6 +74,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
participation = Repo.preload(participation, :recipients)
+ user = User.get_cached_by_id(user.id)
assert [user] == participation.recipients
assert other_user not in participation.recipients