From 6fac6ff7f1aa61a80295097ef2760b98c00313ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Wed, 28 Jan 2026 13:49:34 +0100 Subject: [PATCH 1/5] MastoAPI AccountView: Add mute/block expiry to the relationship object MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- lib/pleroma/user_relationship.ex | 2 +- .../api_spec/schemas/account_relationship.ex | 4 +- .../web/mastodon_api/views/account_view.ex | 76 +++++++++++++------ 3 files changed, 56 insertions(+), 26 deletions(-) diff --git a/lib/pleroma/user_relationship.ex b/lib/pleroma/user_relationship.ex index 07b6e46f7d..cf50256700 100644 --- a/lib/pleroma/user_relationship.ex +++ b/lib/pleroma/user_relationship.ex @@ -45,7 +45,7 @@ defmodule Pleroma.UserRelationship do do: exists?(unquote(relationship_type), source, target) # `def get_block_expire_date/2`, `def get_mute_expire_date/2`, - # `def get_reblog_mute_expire_date/2`, `def get_notification_mute_exists?/2`, + # `def get_reblog_mute_expire_date/2`, `def get_notification_mute_expire_date/2`, # `def get_inverse_subscription_expire_date/2`, `def get_inverse_endorsement_expire_date/2` def unquote(:"get_#{relationship_type}_expire_date")(source, target), do: get_expire_date(unquote(relationship_type), source, target) diff --git a/lib/pleroma/web/api_spec/schemas/account_relationship.ex b/lib/pleroma/web/api_spec/schemas/account_relationship.ex index 68219a0992..247a94bb9d 100644 --- a/lib/pleroma/web/api_spec/schemas/account_relationship.ex +++ b/lib/pleroma/web/api_spec/schemas/account_relationship.ex @@ -26,7 +26,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do requested: %Schema{type: :boolean}, showing_reblogs: %Schema{type: :boolean}, subscribing: %Schema{type: :boolean}, - notifying: %Schema{type: :boolean} + notifying: %Schema{type: :boolean}, + mute_expires_at: %Schema{type: :string, format: "date-time", nullable: true}, + block_expires_at: %Schema{type: :string, format: "date-time", nullable: true} }, example: %{ "blocked_by" => false, diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index a7d994593c..bfcc170d6d 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -96,6 +96,24 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do followed_by = FollowingRelationship.following?(target, reading_user) following = FollowingRelationship.following?(reading_user, target) + blocking = + UserRelationship.exists?( + user_relationships, + :block, + reading_user, + target, + &User.blocks_user?(&1, &2) + ) + + muting = + UserRelationship.exists?( + user_relationships, + :mute, + reading_user, + target, + &User.mutes?(&1, &2) + ) + requested = cond do following -> false @@ -116,14 +134,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do id: to_string(target.id), following: following, followed_by: followed_by, - blocking: - UserRelationship.exists?( - user_relationships, - :block, - reading_user, - target, - &User.blocks_user?(&1, &2) - ), + blocking: blocking, blocked_by: UserRelationship.exists?( user_relationships, @@ -132,14 +143,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do reading_user, &User.blocks_user?(&1, &2) ), - muting: - UserRelationship.exists?( - user_relationships, - :mute, - reading_user, - target, - &User.mutes?(&1, &2) - ), + muting: muting, muting_notifications: UserRelationship.exists?( user_relationships, @@ -174,6 +178,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do &User.endorses?(&1, &2) ) } + |> maybe_put_mute_expires_at(target, reading_user, %{mutes: muting}) + |> maybe_put_block_expires_at(target, reading_user, %{blocks: blocking}) end def render("relationships.json", %{user: user, targets: targets} = opts) do @@ -343,8 +349,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do |> maybe_put_unread_conversation_count(user, opts[:for]) |> maybe_put_unread_notification_count(user, opts[:for]) |> maybe_put_email_address(user, opts[:for]) - |> maybe_put_mute_expires_at(user, opts[:for], opts) - |> maybe_put_block_expires_at(user, opts[:for], opts) + |> maybe_put_mute_expires_at(user, opts[:for], opts, relationship) + |> maybe_put_block_expires_at(user, opts[:for], opts, relationship) |> maybe_show_birthday(user, opts[:for]) end @@ -472,25 +478,47 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do defp maybe_put_email_address(data, _, _), do: data - defp maybe_put_mute_expires_at(data, %User{} = user, target, %{mutes: true}) do + defp maybe_put_mute_expires_at(data, target, user, opts, relationship \\ nil) + + defp maybe_put_mute_expires_at(data, _target, _user, %{mutes: true}, %{ + mute_expires_at: mute_expires_at + }) do + Map.put(data, :mute_expires_at, mute_expires_at) + end + + defp maybe_put_mute_expires_at(data, %User{} = target, user, %{mutes: true}, _relationship) do Map.put( data, :mute_expires_at, - UserRelationship.get_mute_expire_date(target, user) + UserRelationship.get_mute_expire_date(user, target) ) end - defp maybe_put_mute_expires_at(data, _, _, _), do: data + defp maybe_put_mute_expires_at(data, _, _, _, _), do: data - defp maybe_put_block_expires_at(data, %User{} = user, target, %{blocks: true}) do + defp maybe_put_block_expires_at(data, target, user, opts, relationship \\ nil) + + defp maybe_put_block_expires_at(data, _target, _user, %{blocks: true}, %{ + block_expires_at: block_expires_at + }) do + Map.put(data, :block_expires_at, block_expires_at) + end + + defp maybe_put_block_expires_at( + data, + %User{} = target, + %User{} = user, + %{blocks: true}, + _relationship + ) do Map.put( data, :block_expires_at, - UserRelationship.get_block_expire_date(target, user) + UserRelationship.get_block_expire_date(user, target) ) end - defp maybe_put_block_expires_at(data, _, _, _), do: data + defp maybe_put_block_expires_at(data, _, _, _, _), do: data defp maybe_show_birthday(data, %User{id: user_id} = user, %User{id: user_id}) do data From e7a4d5ea66af84aca8f972e65308c231abb4d2a7 Mon Sep 17 00:00:00 2001 From: Phantasm Date: Thu, 22 Jan 2026 23:01:11 +0100 Subject: [PATCH 2/5] MastoAPI AccountView: Add mute/block expiry to the relationship key --- .../mastodon_api/views/account_view_test.exs | 78 ++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/test/pleroma/web/mastodon_api/views/account_view_test.exs b/test/pleroma/web/mastodon_api/views/account_view_test.exs index bd151cc617..c230cf6533 100644 --- a/test/pleroma/web/mastodon_api/views/account_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/account_view_test.exs @@ -439,8 +439,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do following: false, followed_by: false, blocking: false, + block_expires_at: nil, blocked_by: false, muting: false, + mute_expires_at: nil, muting_notifications: false, subscribing: false, notifying: false, @@ -536,6 +538,53 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do test_relationship_rendering(user, other_user, expected) end + test "represent a relationship for the blocking and blocked user with expiry" do + user = insert(:user) + other_user = insert(:user) + date = DateTime.utc_now() |> DateTime.add(24 * 60 * 60) |> DateTime.truncate(:second) + + {:ok, user, other_user} = User.follow(user, other_user) + {:ok, _subscription} = User.subscribe(user, other_user) + {:ok, _user_relationship} = User.block(user, other_user, %{duration: 24 * 60 * 60}) + {:ok, _user_relationship} = User.block(other_user, user) + + expected = + Map.merge( + @blank_response, + %{ + following: false, + blocking: true, + block_expires_at: date, + blocked_by: true, + id: to_string(other_user.id) + } + ) + + test_relationship_rendering(user, other_user, expected) + end + + test "represent a relationship for the muting user with expiry" do + user = insert(:user) + other_user = insert(:user) + date = DateTime.utc_now() |> DateTime.add(24 * 60 * 60) |> DateTime.truncate(:second) + + {:ok, _user_relationship} = + User.mute(user, other_user, %{notifications: true, duration: 24 * 60 * 60}) + + expected = + Map.merge( + @blank_response, + %{ + muting: true, + mute_expires_at: date, + muting_notifications: true, + id: to_string(other_user.id) + } + ) + + test_relationship_rendering(user, other_user, expected) + end + test "represent a relationship for the user blocking a domain" do user = insert(:user) other_user = insert(:user, ap_id: "https://bad.site/users/other_user") @@ -856,12 +905,37 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do User.mute(user, other_user, %{notifications: true, duration: 24 * 60 * 60}) %{ - mute_expires_at: mute_expires_at - } = AccountView.render("show.json", %{user: other_user, for: user, mutes: true}) + pleroma: %{ + relationship: %{ + mute_expires_at: mute_expires_at + } + } + } = AccountView.render("show.json", %{user: other_user, for: user, embed_relationships: true}) assert DateTime.diff( mute_expires_at, DateTime.utc_now() |> DateTime.add(24 * 60 * 60) ) in -3..3 end + + test "renders block expiration date" do + user = insert(:user) + other_user = insert(:user) + + {:ok, _user_relationships} = + User.block(user, other_user, %{duration: 24 * 60 * 60}) + + %{ + pleroma: %{ + relationship: %{ + block_expires_at: block_expires_at + } + } + } = AccountView.render("show.json", %{user: other_user, for: user, embed_relationships: true}) + + assert DateTime.diff( + block_expires_at, + DateTime.utc_now() |> DateTime.add(24 * 60 * 60) + ) in -3..3 + end end From c1e33bfadbc82cc7f019927b4b77a07883501cb7 Mon Sep 17 00:00:00 2001 From: Phantasm Date: Sat, 24 Jan 2026 21:35:27 +0100 Subject: [PATCH 3/5] MastoAPI AccountView AccountController: Add more block/mute expiry tests --- .../controllers/account_controller_test.exs | 103 ++++++++++++++++-- .../mastodon_api/views/account_view_test.exs | 6 +- 2 files changed, 97 insertions(+), 12 deletions(-) diff --git a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs index 02da781dd8..ea98b53a85 100644 --- a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs @@ -1901,7 +1901,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do {:ok, _user_relationships} = User.mute(user, other_user1) {:ok, _user_relationships} = User.mute(user, other_user2) - {:ok, _user_relationships} = User.mute(user, other_user3) + {:ok, _user_relationships} = User.mute(user, other_user3, %{duration: 24 * 60 * 60}) + + date = + DateTime.utc_now() + |> DateTime.add(24 * 60 * 60) + |> DateTime.truncate(:second) + |> DateTime.to_iso8601() result = conn @@ -1937,6 +1943,17 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do |> json_response_and_validate_schema(200) assert [%{"id" => ^id3}] = result + + result = + conn + |> get("/api/v1/mutes") + |> json_response_and_validate_schema(200) + + assert [ + %{"id" => ^id3, "mute_expires_at" => ^date}, + %{"id" => ^id2, "mute_expires_at" => nil}, + %{"id" => ^id1, "mute_expires_at" => nil} + ] = result end test "list of mutes with with_relationships parameter" do @@ -1951,20 +1968,44 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do {:ok, _} = User.mute(user, other_user1) {:ok, _} = User.mute(user, other_user2) - {:ok, _} = User.mute(user, other_user3) + {:ok, _} = User.mute(user, other_user3, %{duration: 24 * 60 * 60}) + + date = + DateTime.utc_now() + |> DateTime.add(24 * 60 * 60) + |> DateTime.truncate(:second) + |> DateTime.to_iso8601() assert [ %{ "id" => ^id3, - "pleroma" => %{"relationship" => %{"muting" => true, "followed_by" => true}} + "pleroma" => %{ + "relationship" => %{ + "muting" => true, + "mute_expires_at" => ^date, + "followed_by" => true + } + } }, %{ "id" => ^id2, - "pleroma" => %{"relationship" => %{"muting" => true, "followed_by" => true}} + "pleroma" => %{ + "relationship" => %{ + "muting" => true, + "mute_expires_at" => nil, + "followed_by" => true + } + } }, %{ "id" => ^id1, - "pleroma" => %{"relationship" => %{"muting" => true, "followed_by" => true}} + "pleroma" => %{ + "relationship" => %{ + "muting" => true, + "mute_expires_at" => nil, + "followed_by" => true + } + } } ] = conn @@ -1980,7 +2021,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do {:ok, _user_relationship} = User.block(user, other_user1) {:ok, _user_relationship} = User.block(user, other_user3) - {:ok, _user_relationship} = User.block(user, other_user2) + {:ok, _user_relationship} = User.block(user, other_user2, %{duration: 24 * 60 * 60}) + + date = + DateTime.utc_now() + |> DateTime.add(24 * 60 * 60) + |> DateTime.truncate(:second) + |> DateTime.to_iso8601() result = conn @@ -2045,6 +2092,18 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do |> json_response_and_validate_schema(200) assert [%{"id" => ^id1}] = result + + result = + conn + |> assign(:user, user) + |> get("api/v1/blocks") + |> json_response_and_validate_schema(200) + + assert [ + %{"id" => ^id3, "block_expires_at" => nil}, + %{"id" => ^id2, "block_expires_at" => ^date}, + %{"id" => ^id1, "block_expires_at" => nil} + ] = result end test "list of blocks with with_relationships parameter" do @@ -2059,20 +2118,44 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do {:ok, _} = User.block(user, other_user1) {:ok, _} = User.block(user, other_user2) - {:ok, _} = User.block(user, other_user3) + {:ok, _} = User.block(user, other_user3, %{duration: 24 * 60 * 60}) + + date = + DateTime.utc_now() + |> DateTime.add(24 * 60 * 60) + |> DateTime.truncate(:second) + |> DateTime.to_iso8601() assert [ %{ "id" => ^id3, - "pleroma" => %{"relationship" => %{"blocking" => true, "followed_by" => false}} + "pleroma" => %{ + "relationship" => %{ + "blocking" => true, + "block_expires_at" => ^date, + "followed_by" => false + } + } }, %{ "id" => ^id2, - "pleroma" => %{"relationship" => %{"blocking" => true, "followed_by" => false}} + "pleroma" => %{ + "relationship" => %{ + "blocking" => true, + "block_expires_at" => nil, + "followed_by" => false + } + } }, %{ "id" => ^id1, - "pleroma" => %{"relationship" => %{"blocking" => true, "followed_by" => false}} + "pleroma" => %{ + "relationship" => %{ + "blocking" => true, + "block_expires_at" => nil, + "followed_by" => false + } + } } ] = conn diff --git a/test/pleroma/web/mastodon_api/views/account_view_test.exs b/test/pleroma/web/mastodon_api/views/account_view_test.exs index c230cf6533..6984442cc6 100644 --- a/test/pleroma/web/mastodon_api/views/account_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/account_view_test.exs @@ -909,7 +909,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do relationship: %{ mute_expires_at: mute_expires_at } - } + }, + mute_expires_at: mute_expires_at } = AccountView.render("show.json", %{user: other_user, for: user, embed_relationships: true}) assert DateTime.diff( @@ -930,7 +931,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do relationship: %{ block_expires_at: block_expires_at } - } + }, + block_expires_at: block_expires_at } = AccountView.render("show.json", %{user: other_user, for: user, embed_relationships: true}) assert DateTime.diff( From bc0c7fb3105d1c9fb0f54390f37f35b613062976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Wed, 28 Jan 2026 13:58:33 +0100 Subject: [PATCH 4/5] Fix tests, relationship should always define `_expires_at` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- lib/pleroma/web/mastodon_api/views/account_view.ex | 2 ++ test/pleroma/web/mastodon_api/views/account_view_test.exs | 6 ++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index bfcc170d6d..29c63eb601 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -143,6 +143,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do reading_user, &User.blocks_user?(&1, &2) ), + block_expires_at: nil, muting: muting, muting_notifications: UserRelationship.exists?( @@ -152,6 +153,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do target, &User.muted_notifications?(&1, &2) ), + mute_expires_at: nil, subscribing: subscribing, notifying: subscribing, requested: requested, diff --git a/test/pleroma/web/mastodon_api/views/account_view_test.exs b/test/pleroma/web/mastodon_api/views/account_view_test.exs index 6984442cc6..c230cf6533 100644 --- a/test/pleroma/web/mastodon_api/views/account_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/account_view_test.exs @@ -909,8 +909,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do relationship: %{ mute_expires_at: mute_expires_at } - }, - mute_expires_at: mute_expires_at + } } = AccountView.render("show.json", %{user: other_user, for: user, embed_relationships: true}) assert DateTime.diff( @@ -931,8 +930,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do relationship: %{ block_expires_at: block_expires_at } - }, - block_expires_at: block_expires_at + } } = AccountView.render("show.json", %{user: other_user, for: user, embed_relationships: true}) assert DateTime.diff( From 5001fb3a78737273fef7c36a9507ba0e35fd47f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Wed, 28 Jan 2026 14:02:55 +0100 Subject: [PATCH 5/5] Update changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- changelog.d/relationship-expires-at.change | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/relationship-expires-at.change diff --git a/changelog.d/relationship-expires-at.change b/changelog.d/relationship-expires-at.change new file mode 100644 index 0000000000..286dba1970 --- /dev/null +++ b/changelog.d/relationship-expires-at.change @@ -0,0 +1 @@ +Add mute/block expiry to the relationship object