Compare commits

...

20 Commits

Author SHA1 Message Date
Moon Man aee44ac25e properly get actor from key id for Streams. 2024-05-21 23:50:35 +00:00
lain 7fca598268 Merge branch 'status-notification-type' into 'develop'
Add "status" notification type

See merge request pleroma/pleroma!3659
2024-05-21 05:01:45 +00:00
marcin mikołajczak 36fa0debfe Fix `get_notified_from`
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2024-05-20 23:25:50 +02:00
lain e8cd6662eb Merge branch 'familiar-followers' into 'develop'
Implement `/api/v1/accounts/familiar_followers`

See merge request pleroma/pleroma!4098
2024-05-19 12:05:55 +00:00
lain 401aca2548 Merge branch 'mark-read' into 'develop'
PleromaAPI: Simplify marking notifications as read

See merge request pleroma/pleroma!4111
2024-05-19 07:48:32 +00:00
Mark Felder d07d49227f PleromaAPI: marking notifications as read no longer returns notifications 2024-05-18 18:17:35 +00:00
marcin mikołajczak 2e76ceb5b4 Merge remote-tracking branch 'origin/develop' into status-notification-type
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2024-05-18 11:30:25 +02:00
feld 99eab1fa2a Merge branch 'revert-e944b152' into 'develop'
Revert "Merge branch 'strip-object-actor' into 'develop'"

See merge request pleroma/pleroma!4112
2024-05-16 23:34:00 +00:00
feld 9988dc2227 Revert "Merge branch 'strip-object-actor' into 'develop'"
This reverts merge request !4105
2024-05-16 23:33:48 +00:00
feld 7de657ac45 Merge branch 'bad-mrf' into 'develop'
Startup detection for configured MRF modules that are missing or incorrectly defined

See merge request pleroma/pleroma!4110
2024-05-16 20:37:37 +00:00
Mark Felder 7f8a9329e5 Startup detection for configured MRF modules that are missing or incorrectly defined 2024-05-16 16:13:29 -04:00
Haelwenn 88412daf11 Apply @lanodan's suggestion
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2024-04-25 12:34:12 +02:00
marcin mikołajczak 9e6cf45906 /api/v1/accounts/familiar_followers
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2024-04-06 11:43:56 +02:00
marcin mikołajczak 1ed8ae2d8e Add changelog
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2024-01-31 22:55:58 +01:00
marcin mikołajczak 226e53fdd7 Merge remote-tracking branch 'origin/develop' into status-notification-type
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2024-01-31 22:19:33 +01:00
marcin mikołajczak 9363ef53a3 Add test for 'status' notification type for NotificationView
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-05-14 15:02:58 +02:00
marcin mikołajczak 78d1105bff Fix down migration
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-19 22:02:38 +01:00
marcin mikołajczak 92592c25c2 Merge remote-tracking branch 'pleroma/develop' into status-notification-type
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-19 22:02:03 +01:00
marcin mikołajczak 3ed39e3109 Add test
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2022-07-08 21:28:23 +02:00
marcin mikołajczak 9423052e92 Add "status" notification type
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2022-04-25 14:08:31 +02:00
30 changed files with 409 additions and 60 deletions

View File

@ -0,0 +1 @@
Implement `/api/v1/accounts/familiar_followers`

View File

@ -0,0 +1 @@
The query for marking notifications as read has been simplified

View File

@ -0,0 +1 @@
Startup detection for configured MRF modules that are missing or incorrectly defined

View File

@ -0,0 +1 @@
Add "status" notification type

View File

@ -1 +0,0 @@
Strip actor property from objects before federating

View File

@ -28,6 +28,7 @@ def verify! do
|> check_welcome_message_config!() |> check_welcome_message_config!()
|> check_rum!() |> check_rum!()
|> check_repo_pool_size!() |> check_repo_pool_size!()
|> check_mrfs()
|> handle_result() |> handle_result()
end end
@ -234,4 +235,25 @@ defp check_filter(filter, command_required) do
true true
end end
end end
defp check_mrfs(:ok) do
mrfs = Config.get!([:mrf, :policies])
missing_mrfs =
Enum.reduce(mrfs, [], fn x, acc ->
if Code.ensure_compiled(x) do
acc
else
acc ++ [x]
end
end)
if Enum.empty?(missing_mrfs) do
:ok
else
{:error, "The following MRF modules are configured but missing: #{inspect(missing_mrfs)}"}
end
end
defp check_mrfs(result), do: result
end end

View File

@ -9,7 +9,6 @@ defmodule Pleroma.Constants do
const(object_internal_fields, const(object_internal_fields,
do: [ do: [
"actor",
"reactions", "reactions",
"reaction_count", "reaction_count",
"likes", "likes",

View File

@ -73,6 +73,7 @@ def unread_notifications_count(%User{id: user_id}) do
pleroma:report pleroma:report
reblog reblog
poll poll
status
} }
def changeset(%Notification{} = notification, attrs) do def changeset(%Notification{} = notification, attrs) do
@ -280,15 +281,10 @@ def set_read_up_to(%{id: user_id} = user, id) do
select: n.id select: n.id
) )
{:ok, %{ids: {_, notification_ids}}} =
Multi.new() Multi.new()
|> Multi.update_all(:ids, query, set: [seen: true, updated_at: NaiveDateTime.utc_now()]) |> Multi.update_all(:ids, query, set: [seen: true, updated_at: NaiveDateTime.utc_now()])
|> Marker.multi_set_last_read_id(user, "notifications") |> Marker.multi_set_last_read_id(user, "notifications")
|> Repo.transaction() |> Repo.transaction()
for_user_query(user)
|> where([n], n.id in ^notification_ids)
|> Repo.all()
end end
@spec read_one(User.t(), String.t()) :: @spec read_one(User.t(), String.t()) ::
@ -299,10 +295,6 @@ def read_one(%User{} = user, notification_id) do
|> Multi.update(:update, changeset(notification, %{seen: true})) |> Multi.update(:update, changeset(notification, %{seen: true}))
|> Marker.multi_set_last_read_id(user, "notifications") |> Marker.multi_set_last_read_id(user, "notifications")
|> Repo.transaction() |> Repo.transaction()
|> case do
{:ok, %{update: notification}} -> {:ok, notification}
{:error, :update, changeset, _} -> {:error, changeset}
end
end end
end end
@ -384,10 +376,15 @@ def create_notifications(_), do: {:ok, []}
defp do_create_notifications(%Activity{} = activity) do defp do_create_notifications(%Activity{} = activity) do
enabled_receivers = get_notified_from_activity(activity) enabled_receivers = get_notified_from_activity(activity)
enabled_subscribers = get_notified_subscribers_from_activity(activity)
notifications = notifications =
Enum.map(enabled_receivers, fn user -> (Enum.map(enabled_receivers, fn user ->
create_notification(activity, user) create_notification(activity, user)
end) end) ++
Enum.map(enabled_subscribers -- enabled_receivers, fn user ->
create_notification(activity, user, type: "status")
end))
|> Enum.reject(&is_nil/1) |> Enum.reject(&is_nil/1)
{:ok, notifications} {:ok, notifications}
@ -520,7 +517,25 @@ def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, lo
Enum.filter(potential_receivers, fn u -> u.ap_id in notification_enabled_ap_ids end) Enum.filter(potential_receivers, fn u -> u.ap_id in notification_enabled_ap_ids end)
end end
def get_notified_from_activity(_, _local_only), do: {[], []} def get_notified_from_activity(_, _local_only), do: []
def get_notified_subscribers_from_activity(activity, local_only \\ true)
def get_notified_subscribers_from_activity(
%Activity{data: %{"type" => "Create"}} = activity,
local_only
) do
notification_enabled_ap_ids =
[]
|> Utils.maybe_notify_subscribers(activity)
potential_receivers =
User.get_users_from_set(notification_enabled_ap_ids, local_only: local_only)
Enum.filter(potential_receivers, fn u -> u.ap_id in notification_enabled_ap_ids end)
end
def get_notified_subscribers_from_activity(_, _), do: []
# For some activities, only notify the author of the object # For some activities, only notify the author of the object
def get_potential_receiver_ap_ids(%{data: %{"type" => type, "object" => object_id}}) def get_potential_receiver_ap_ids(%{data: %{"type" => type, "object" => object_id}})
@ -563,7 +578,6 @@ def get_potential_receiver_ap_ids(activity) do
[] []
|> Utils.maybe_notify_to_recipients(activity) |> Utils.maybe_notify_to_recipients(activity)
|> Utils.maybe_notify_mentioned_recipients(activity) |> Utils.maybe_notify_mentioned_recipients(activity)
|> Utils.maybe_notify_subscribers(activity)
|> Utils.maybe_notify_followers(activity) |> Utils.maybe_notify_followers(activity)
|> Enum.uniq() |> Enum.uniq()
end end

View File

@ -17,6 +17,7 @@ def key_id_to_actor_id(key_id) do
key_id key_id
|> URI.parse() |> URI.parse()
|> Map.put(:fragment, nil) |> Map.put(:fragment, nil)
|> remove_query()
|> remove_suffix(@known_suffixes) |> remove_suffix(@known_suffixes)
maybe_ap_id = URI.to_string(uri) maybe_ap_id = URI.to_string(uri)
@ -33,6 +34,23 @@ def key_id_to_actor_id(key_id) do
end end
end end
defp remove_query(uri) do
if uri.query do
new_query =
URI.decode_query(uri.query)
|> Map.delete("operation")
|> URI.encode_query()
|> case do
"" -> nil
query -> query
end
Map.put(uri, :query, new_query)
else
uri
end
end
defp remove_suffix(uri, [test | rest]) do defp remove_suffix(uri, [test | rest]) do
if not is_nil(uri.path) and String.ends_with?(uri.path, test) do if not is_nil(uri.path) and String.ends_with?(uri.path, test) do
Map.put(uri, :path, String.replace(uri.path, test, "")) Map.put(uri, :path, String.replace(uri.path, test, ""))

View File

@ -1404,6 +1404,40 @@ def get_friends_ids(%User{} = user, page \\ nil) do
|> Repo.all() |> Repo.all()
end end
@spec get_familiar_followers_query(User.t(), User.t(), pos_integer() | nil) :: Ecto.Query.t()
def get_familiar_followers_query(%User{} = user, %User{} = current_user, nil) do
friends =
get_friends_query(current_user)
|> where([u], not u.hide_follows)
|> select([u], u.id)
User.Query.build(%{is_active: true})
|> where([u], u.id not in ^[user.id, current_user.id])
|> join(:inner, [u], r in FollowingRelationship,
as: :followers_relationships,
on: r.following_id == ^user.id and r.follower_id == u.id
)
|> where([followers_relationships: r], r.state == ^:follow_accept)
|> where([followers_relationships: r], r.follower_id in subquery(friends))
end
def get_familiar_followers_query(%User{} = user, %User{} = current_user, page) do
user
|> get_familiar_followers_query(current_user, nil)
|> User.Query.paginate(page, 20)
end
@spec get_familiar_followers_query(User.t(), User.t()) :: Ecto.Query.t()
def get_familiar_followers_query(%User{} = user, %User{} = current_user),
do: get_familiar_followers_query(user, current_user, nil)
@spec get_familiar_followers(User.t(), User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
def get_familiar_followers(%User{} = user, %User{} = current_user, page \\ nil) do
user
|> get_familiar_followers_query(current_user, page)
|> Repo.all()
end
def increase_note_count(%User{} = user) do def increase_note_count(%User{} = user) do
User User
|> where(id: ^user.id) |> where(id: ^user.id)

View File

@ -11,6 +11,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
alias Pleroma.Web.ApiSpec.Schemas.ActorType alias Pleroma.Web.ApiSpec.Schemas.ActorType
alias Pleroma.Web.ApiSpec.Schemas.ApiError alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
alias Pleroma.Web.ApiSpec.Schemas.List alias Pleroma.Web.ApiSpec.Schemas.List
alias Pleroma.Web.ApiSpec.Schemas.Status alias Pleroma.Web.ApiSpec.Schemas.Status
alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
@ -513,6 +514,48 @@ def identity_proofs_operation do
} }
end end
def familiar_followers_operation do
%Operation{
tags: ["Retrieve account information"],
summary: "Followers that you follow",
operationId: "AccountController.familiar_followers",
description:
"Obtain a list of all accounts that follow a given account, filtered for accounts you follow.",
security: [%{"oAuth" => ["read:follows"]}],
parameters: [
Operation.parameter(
:id,
:query,
%Schema{
oneOf: [%Schema{type: :array, items: %Schema{type: :string}}, %Schema{type: :string}]
},
"Account IDs",
example: "123"
)
],
responses: %{
200 =>
Operation.response("Accounts", "application/json", %Schema{
title: "ArrayOfAccounts",
type: :array,
items: %Schema{
title: "Account",
type: :object,
properties: %{
id: FlakeID,
accounts: %Schema{
title: "ArrayOfAccounts",
type: :array,
items: Account,
example: [Account.schema().example]
}
}
}
})
}
}
end
defp create_request do defp create_request do
%Schema{ %Schema{
title: "AccountCreateRequest", title: "AccountCreateRequest",

View File

@ -202,7 +202,8 @@ defp notification_type do
"pleroma:report", "pleroma:report",
"move", "move",
"follow_request", "follow_request",
"poll" "poll",
"status"
], ],
description: """ description: """
The type of event that resulted in the notification. The type of event that resulted in the notification.
@ -216,6 +217,7 @@ defp notification_type do
- `pleroma:emoji_reaction` - Someone reacted with emoji to your status - `pleroma:emoji_reaction` - Someone reacted with emoji to your status
- `pleroma:chat_mention` - Someone mentioned you in a chat message - `pleroma:chat_mention` - Someone mentioned you in a chat message
- `pleroma:report` - Someone was reported - `pleroma:report` - Someone was reported
- `status` - Someone you are subscribed to created a status
""" """
} }
end end

View File

@ -5,7 +5,6 @@
defmodule Pleroma.Web.ApiSpec.PleromaNotificationOperation do defmodule Pleroma.Web.ApiSpec.PleromaNotificationOperation do
alias OpenApiSpex.Operation alias OpenApiSpex.Operation
alias OpenApiSpex.Schema alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.NotificationOperation
alias Pleroma.Web.ApiSpec.Schemas.ApiError alias Pleroma.Web.ApiSpec.Schemas.ApiError
import Pleroma.Web.ApiSpec.Helpers import Pleroma.Web.ApiSpec.Helpers
@ -35,12 +34,7 @@ def mark_as_read_operation do
Operation.response( Operation.response(
"A Notification or array of Notifications", "A Notification or array of Notifications",
"application/json", "application/json",
%Schema{ %Schema{type: :string}
anyOf: [
%Schema{type: :array, items: NotificationOperation.notification()},
NotificationOperation.notification()
]
}
), ),
400 => Operation.response("Bad Request", "application/json", ApiError) 400 => Operation.response("Bad Request", "application/json", ApiError)
} }

View File

@ -72,7 +72,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
%{scopes: ["follow", "write:blocks"]} when action in [:block, :unblock] %{scopes: ["follow", "write:blocks"]} when action in [:block, :unblock]
) )
plug(OAuthScopesPlug, %{scopes: ["read:follows"]} when action == :relationships) plug(
OAuthScopesPlug,
%{scopes: ["read:follows"]} when action in [:relationships, :familiar_followers]
)
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
@ -629,6 +632,35 @@ def endorsements(%{assigns: %{user: user}} = conn, params) do
) )
end end
@doc "GET /api/v1/accounts/familiar_followers"
def familiar_followers(
%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn,
_id
) do
users =
User.get_all_by_ids(List.wrap(id))
|> Enum.map(&%{id: &1.id, accounts: get_familiar_followers(&1, user)})
conn
|> render("familiar_followers.json",
for: user,
users: users,
as: :user
)
end
defp get_familiar_followers(%{id: id} = user, %{id: id}) do
User.get_familiar_followers(user, user)
end
defp get_familiar_followers(%{hide_followers: true}, _current_user) do
[]
end
defp get_familiar_followers(user, current_user) do
User.get_familiar_followers(user, current_user)
end
@doc "GET /api/v1/identity_proofs" @doc "GET /api/v1/identity_proofs"
def identity_proofs(conn, params), do: MastodonAPIController.empty_array(conn, params) def identity_proofs(conn, params), do: MastodonAPIController.empty_array(conn, params)
end end

View File

@ -34,6 +34,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
pleroma:emoji_reaction pleroma:emoji_reaction
poll poll
update update
status
} }
# GET /api/v1/notifications # GET /api/v1/notifications

View File

@ -193,6 +193,25 @@ def render("relationships.json", %{user: user, targets: targets} = opts) do
render_many(targets, AccountView, "relationship.json", render_opts) render_many(targets, AccountView, "relationship.json", render_opts)
end end
def render("familiar_followers.json", %{users: users} = opts) do
opts =
opts
|> Map.merge(%{as: :user})
|> Map.delete(:users)
users
|> render_many(AccountView, "familiar_followers.json", opts)
end
def render("familiar_followers.json", %{user: %{id: id, accounts: accounts}} = opts) do
accounts =
accounts
|> render_many(AccountView, "show.json", opts)
|> Enum.filter(&Enum.any?/1)
%{id: id, accounts: accounts}
end
defp do_render("show.json", %{user: user} = opts) do defp do_render("show.json", %{user: user} = opts) do
self = opts[:for] == user self = opts[:for] == user

View File

@ -108,6 +108,9 @@ def render(
"mention" -> "mention" ->
put_status(response, activity, reading_user, status_render_opts) put_status(response, activity, reading_user, status_render_opts)
"status" ->
put_status(response, activity, reading_user, status_render_opts)
"favourite" -> "favourite" ->
put_status(response, parent_activity_fn.(), reading_user, status_render_opts) put_status(response, parent_activity_fn.(), reading_user, status_render_opts)

View File

@ -23,8 +23,9 @@ def mark_as_read(
} = conn, } = conn,
_ _
) do ) do
with {:ok, notification} <- Notification.read_one(user, notification_id) do with {:ok, _} <- Notification.read_one(user, notification_id) do
render(conn, "show.json", notification: notification, for: user) conn
|> json("ok")
else else
{:error, message} -> {:error, message} ->
conn conn
@ -38,11 +39,14 @@ def mark_as_read(
conn, conn,
_ _
) do ) do
notifications = with {:ok, _} <- Notification.set_read_up_to(user, max_id) do
user conn
|> Notification.set_read_up_to(max_id) |> json("ok")
|> Enum.take(80) else
{:error, message} ->
render(conn, "index.json", notifications: notifications, for: user) conn
|> put_status(:bad_request)
|> json(%{"error" => message})
end
end end
end end

View File

@ -192,6 +192,7 @@ def format_title(%{activity: %{data: %{"directMessage" => true}}}, _mastodon_typ
def format_title(%{type: type}, mastodon_type) do def format_title(%{type: type}, mastodon_type) do
case mastodon_type || type do case mastodon_type || type do
"mention" -> "New Mention" "mention" -> "New Mention"
"status" -> "New Status"
"follow" -> "New Follower" "follow" -> "New Follower"
"follow_request" -> "New Follow Request" "follow_request" -> "New Follow Request"
"reblog" -> "New Repeat" "reblog" -> "New Repeat"

View File

@ -638,6 +638,7 @@ defmodule Pleroma.Web.Router do
patch("/accounts/update_credentials", AccountController, :update_credentials) patch("/accounts/update_credentials", AccountController, :update_credentials)
get("/accounts/relationships", AccountController, :relationships) get("/accounts/relationships", AccountController, :relationships)
get("/accounts/familiar_followers", AccountController, :familiar_followers)
get("/accounts/:id/lists", AccountController, :lists) get("/accounts/:id/lists", AccountController, :lists)
get("/accounts/:id/identity_proofs", AccountController, :identity_proofs) get("/accounts/:id/identity_proofs", AccountController, :identity_proofs)
get("/endorsements", AccountController, :endorsements) get("/endorsements", AccountController, :endorsements)

View File

@ -0,0 +1,51 @@
defmodule Pleroma.Repo.Migrations.AddStatusToNotificationsEnum do
use Ecto.Migration
@disable_ddl_transaction true
def up do
"""
alter type notification_type add value 'status'
"""
|> execute()
end
def down do
alter table(:notifications) do
modify(:type, :string)
end
"""
delete from notifications where type = 'status'
"""
|> execute()
"""
drop type if exists notification_type
"""
|> execute()
"""
create type notification_type as enum (
'follow',
'follow_request',
'mention',
'move',
'pleroma:emoji_reaction',
'pleroma:chat_mention',
'reblog',
'favourite',
'pleroma:report',
'poll',
'update'
)
"""
|> execute()
"""
alter table notifications
alter column type type notification_type using (type::notification_type)
"""
|> execute()
end
end

View File

@ -1,10 +1,10 @@
{ {
"actor": "http://mastodon.example.org/users/admin", "actor": "http://2hu.gensokyo/users/raymoo",
"id": "http://mastodon.example.org/objects/1", "id": "http://2hu.gensokyo/objects/1",
"object": { "object": {
"attributedTo": "http://mastodon.example.org/users/admin", "attributedTo": "http://2hu.gensokyo/users/raymoo",
"content": "You expected a cute girl? Too bad. <script>alert('XSS')</script>", "content": "You expected a cute girl? Too bad. <script>alert('XSS')</script>",
"id": "http://mastodon.example.org/objects/2", "id": "http://2hu.gensokyo/objects/2",
"published": "2020-02-12T14:08:20Z", "published": "2020-02-12T14:08:20Z",
"to": [ "to": [
"http://2hu.gensokyo/users/marisa" "http://2hu.gensokyo/users/marisa"

View File

@ -112,6 +112,7 @@ test "it creates a notification for subscribed users" do
{:ok, [notification]} = Notification.create_notifications(status) {:ok, [notification]} = Notification.create_notifications(status)
assert notification.user_id == subscriber.id assert notification.user_id == subscriber.id
assert notification.type == "status"
end end
test "does not create a notification for subscribed users if status is a reply" do test "does not create a notification for subscribed users if status is a reply" do
@ -136,6 +137,21 @@ test "does not create a notification for subscribed users if status is a reply"
assert Enum.empty?(subscriber_notifications) assert Enum.empty?(subscriber_notifications)
end end
test "does not create subscriber notification if mentioned" do
user = insert(:user)
subscriber = insert(:user)
User.subscribe(subscriber, user)
{:ok, status} = CommonAPI.post(user, %{status: "mentioning @#{subscriber.nickname}"})
{:ok, [notification] = notifications} = Notification.create_notifications(status)
assert length(notifications) == 1
assert notification.user_id == subscriber.id
assert notification.type == "mention"
end
test "it sends edited notifications to those who repeated a status" do test "it sends edited notifications to those who repeated a status" do
user = insert(:user) user = insert(:user)
repeated_user = insert(:user) repeated_user = insert(:user)
@ -449,9 +465,7 @@ test "it sets all notifications as read up to a specified notification ID" do
status: "hey yet again @#{other_user.nickname}!" status: "hey yet again @#{other_user.nickname}!"
}) })
[_, read_notification] = Notification.set_read_up_to(other_user, n2.id) Notification.set_read_up_to(other_user, n2.id)
assert read_notification.activity.object
[n3, n2, n1] = Notification.for_user(other_user) [n3, n2, n1] = Notification.for_user(other_user)

View File

@ -221,6 +221,7 @@ test "it creates a zip archive with user data" do
"orderedItems" => [ "orderedItems" => [
%{ %{
"object" => %{ "object" => %{
"actor" => "http://cofe.io/users/cofe",
"content" => "status1", "content" => "status1",
"type" => "Note" "type" => "Note"
}, },
@ -228,6 +229,7 @@ test "it creates a zip archive with user data" do
}, },
%{ %{
"object" => %{ "object" => %{
"actor" => "http://cofe.io/users/cofe",
"content" => "status2" "content" => "status2"
} }
}, },

View File

@ -2894,6 +2894,20 @@ test "should report error on non-existing alias" do
end end
end end
describe "get_familiar_followers/3" do
test "returns familiar followers for a pair of users" do
user1 = insert(:user)
%{id: id2} = user2 = insert(:user)
user3 = insert(:user)
_user4 = insert(:user)
User.follow(user1, user2)
User.follow(user2, user3)
assert [%{id: ^id2}] = User.get_familiar_followers(user3, user1)
end
end
describe "account endorsements" do describe "account endorsements" do
test "it pins people" do test "it pins people" do
user = insert(:user) user = insert(:user)

View File

@ -116,6 +116,8 @@ test "it fetches the actor if they aren't in our system" do
data = data =
File.read!("test/fixtures/create-chat-message.json") File.read!("test/fixtures/create-chat-message.json")
|> Jason.decode!() |> Jason.decode!()
|> Map.put("actor", "http://mastodon.example.org/users/admin")
|> put_in(["object", "actor"], "http://mastodon.example.org/users/admin")
_recipient = insert(:user, ap_id: List.first(data["to"]), local: true) _recipient = insert(:user, ap_id: List.first(data["to"]), local: true)

View File

@ -169,7 +169,7 @@ test "it inlines private announced objects" do
{:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data) {:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data)
assert modified["object"]["content"] == "hey" assert modified["object"]["content"] == "hey"
assert activity.actor == modified["object"]["attributedTo"] assert modified["object"]["actor"] == modified["object"]["attributedTo"]
end end
test "it turns mentions into tags" do test "it turns mentions into tags" do
@ -220,7 +220,7 @@ test "it sets the 'attributedTo' property to the actor of the object if it doesn
{:ok, activity} = CommonAPI.post(user, %{status: "hey"}) {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
assert activity.actor == modified["object"]["attributedTo"] assert modified["object"]["actor"] == modified["object"]["attributedTo"]
end end
test "it strips internal hashtag data" do test "it strips internal hashtag data" do
@ -266,7 +266,6 @@ test "it strips internal fields" do
assert is_nil(modified["object"]["announcements"]) assert is_nil(modified["object"]["announcements"])
assert is_nil(modified["object"]["announcement_count"]) assert is_nil(modified["object"]["announcement_count"])
assert is_nil(modified["object"]["generator"]) assert is_nil(modified["object"]["generator"])
assert is_nil(modified["object"]["actor"])
end end
test "it strips internal fields of article" do test "it strips internal fields of article" do

View File

@ -2172,6 +2172,55 @@ test "max pinned accounts", %{user: user, conn: conn} do
end end
end end
describe "familiar followers" do
setup do: oauth_access(["read:follows"])
test "fetch user familiar followers", %{user: user, conn: conn} do
%{id: id1} = other_user1 = insert(:user)
%{id: id2} = other_user2 = insert(:user)
_ = insert(:user)
User.follow(user, other_user1)
User.follow(other_user1, other_user2)
assert [%{"accounts" => [%{"id" => ^id1}], "id" => ^id2}] =
conn
|> put_req_header("content-type", "application/json")
|> get("/api/v1/accounts/familiar_followers?id[]=#{id2}")
|> json_response_and_validate_schema(200)
end
test "returns empty array if followers are hidden", %{user: user, conn: conn} do
other_user1 = insert(:user, hide_follows: true)
%{id: id2} = other_user2 = insert(:user)
_ = insert(:user)
User.follow(user, other_user1)
User.follow(other_user1, other_user2)
assert [%{"accounts" => [], "id" => ^id2}] =
conn
|> put_req_header("content-type", "application/json")
|> get("/api/v1/accounts/familiar_followers?id[]=#{id2}")
|> json_response_and_validate_schema(200)
end
test "it respects hide_followers", %{user: user, conn: conn} do
other_user1 = insert(:user)
%{id: id2} = other_user2 = insert(:user, hide_followers: true)
_ = insert(:user)
User.follow(user, other_user1)
User.follow(other_user1, other_user2)
assert [%{"accounts" => [], "id" => ^id2}] =
conn
|> put_req_header("content-type", "application/json")
|> get("/api/v1/accounts/familiar_followers?id[]=#{id2}")
|> json_response_and_validate_schema(200)
end
end
describe "remove from followers" do describe "remove from followers" do
setup do: oauth_access(["follow"]) setup do: oauth_access(["follow"])

View File

@ -331,4 +331,31 @@ test "muted notification" do
test_notifications_rendering([notification], user, [expected]) test_notifications_rendering([notification], user, [expected])
end end
test "Subscribed status notification" do
user = insert(:user)
subscriber = insert(:user)
User.subscribe(subscriber, user)
{:ok, activity} = CommonAPI.post(user, %{status: "hi"})
{:ok, [notification]} = Notification.create_notifications(activity)
user = User.get_cached_by_id(user.id)
expected = %{
id: to_string(notification.id),
pleroma: %{is_seen: false, is_muted: false},
type: "status",
account:
AccountView.render("show.json", %{
user: user,
for: subscriber
}),
status: StatusView.render("show.json", %{activity: activity, for: subscriber}),
created_at: Utils.to_masto_date(notification.inserted_at)
}
test_notifications_rendering([notification], subscriber, [expected])
end
end end

View File

@ -21,13 +21,11 @@ test "it marks a single notification as read", %{user: user1, conn: conn} do
{:ok, [notification1]} = Notification.create_notifications(activity1) {:ok, [notification1]} = Notification.create_notifications(activity1)
{:ok, [notification2]} = Notification.create_notifications(activity2) {:ok, [notification2]} = Notification.create_notifications(activity2)
response =
conn conn
|> put_req_header("content-type", "application/json") |> put_req_header("content-type", "application/json")
|> post("/api/v1/pleroma/notifications/read", %{id: notification1.id}) |> post("/api/v1/pleroma/notifications/read", %{id: notification1.id})
|> json_response_and_validate_schema(:ok) |> json_response_and_validate_schema(:ok)
assert %{"pleroma" => %{"is_seen" => true}} = response
assert Repo.get(Notification, notification1.id).seen assert Repo.get(Notification, notification1.id).seen
refute Repo.get(Notification, notification2.id).seen refute Repo.get(Notification, notification2.id).seen
end end
@ -40,14 +38,17 @@ test "it marks multiple notifications as read", %{user: user1, conn: conn} do
[notification3, notification2, notification1] = Notification.for_user(user1, %{limit: 3}) [notification3, notification2, notification1] = Notification.for_user(user1, %{limit: 3})
[response1, response2] = refute Repo.get(Notification, notification1.id).seen
refute Repo.get(Notification, notification2.id).seen
refute Repo.get(Notification, notification3.id).seen
conn conn
|> put_req_header("content-type", "application/json") |> put_req_header("content-type", "application/json")
|> post("/api/v1/pleroma/notifications/read", %{max_id: notification2.id}) |> post("/api/v1/pleroma/notifications/read", %{max_id: notification2.id})
|> json_response_and_validate_schema(:ok) |> json_response_and_validate_schema(:ok)
assert %{"pleroma" => %{"is_seen" => true}} = response1 [notification3, notification2, notification1] = Notification.for_user(user1, %{limit: 3})
assert %{"pleroma" => %{"is_seen" => true}} = response2
assert Repo.get(Notification, notification1.id).seen assert Repo.get(Notification, notification1.id).seen
assert Repo.get(Notification, notification2.id).seen assert Repo.get(Notification, notification2.id).seen
refute Repo.get(Notification, notification3.id).seen refute Repo.get(Notification, notification3.id).seen