Merge remote-tracking branch 'upstream/develop' into spc2
This commit is contained in:
commit
9bbf4c9238
|
@ -0,0 +1 @@
|
|||
Implement `/api/v1/accounts/familiar_followers`
|
|
@ -0,0 +1 @@
|
|||
The query for marking notifications as read has been simplified
|
|
@ -280,15 +280,10 @@ def set_read_up_to(%{id: user_id} = user, id) do
|
|||
select: n.id
|
||||
)
|
||||
|
||||
{:ok, %{ids: {_, notification_ids}}} =
|
||||
Multi.new()
|
||||
|> Multi.update_all(:ids, query, set: [seen: true, updated_at: NaiveDateTime.utc_now()])
|
||||
|> Marker.multi_set_last_read_id(user, "notifications")
|
||||
|> Repo.transaction()
|
||||
|
||||
for_user_query(user)
|
||||
|> where([n], n.id in ^notification_ids)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@spec read_one(User.t(), String.t()) ::
|
||||
|
@ -299,10 +294,6 @@ def read_one(%User{} = user, notification_id) do
|
|||
|> Multi.update(:update, changeset(notification, %{seen: true}))
|
||||
|> Marker.multi_set_last_read_id(user, "notifications")
|
||||
|> Repo.transaction()
|
||||
|> case do
|
||||
{:ok, %{update: notification}} -> {:ok, notification}
|
||||
{:error, :update, changeset, _} -> {:error, changeset}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1404,6 +1404,40 @@ def get_friends_ids(%User{} = user, page \\ nil) do
|
|||
|> Repo.all()
|
||||
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
|
||||
User
|
||||
|> where(id: ^user.id)
|
||||
|
|
|
@ -11,6 +11,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
alias Pleroma.Web.ApiSpec.Schemas.ActorType
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
|
||||
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
|
||||
alias Pleroma.Web.ApiSpec.Schemas.List
|
||||
alias Pleroma.Web.ApiSpec.Schemas.Status
|
||||
alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
|
||||
|
@ -513,6 +514,48 @@ def identity_proofs_operation do
|
|||
}
|
||||
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
|
||||
%Schema{
|
||||
title: "AccountCreateRequest",
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
defmodule Pleroma.Web.ApiSpec.PleromaNotificationOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.NotificationOperation
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
|
||||
import Pleroma.Web.ApiSpec.Helpers
|
||||
|
@ -35,12 +34,7 @@ def mark_as_read_operation do
|
|||
Operation.response(
|
||||
"A Notification or array of Notifications",
|
||||
"application/json",
|
||||
%Schema{
|
||||
anyOf: [
|
||||
%Schema{type: :array, items: NotificationOperation.notification()},
|
||||
NotificationOperation.notification()
|
||||
]
|
||||
}
|
||||
%Schema{type: :string}
|
||||
),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError)
|
||||
}
|
||||
|
|
|
@ -72,7 +72,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
%{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(
|
||||
OAuthScopesPlug,
|
||||
|
@ -629,6 +632,35 @@ def endorsements(%{assigns: %{user: user}} = conn, params) do
|
|||
)
|
||||
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"
|
||||
def identity_proofs(conn, params), do: MastodonAPIController.empty_array(conn, params)
|
||||
end
|
||||
|
|
|
@ -195,6 +195,25 @@ def render("relationships.json", %{user: user, targets: targets} = opts) do
|
|||
render_many(targets, AccountView, "relationship.json", render_opts)
|
||||
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
|
||||
self = opts[:for] == user
|
||||
|
||||
|
|
|
@ -23,8 +23,9 @@ def mark_as_read(
|
|||
} = conn,
|
||||
_
|
||||
) do
|
||||
with {:ok, notification} <- Notification.read_one(user, notification_id) do
|
||||
render(conn, "show.json", notification: notification, for: user)
|
||||
with {:ok, _} <- Notification.read_one(user, notification_id) do
|
||||
conn
|
||||
|> json("ok")
|
||||
else
|
||||
{:error, message} ->
|
||||
conn
|
||||
|
@ -38,11 +39,14 @@ def mark_as_read(
|
|||
conn,
|
||||
_
|
||||
) do
|
||||
notifications =
|
||||
user
|
||||
|> Notification.set_read_up_to(max_id)
|
||||
|> Enum.take(80)
|
||||
|
||||
render(conn, "index.json", notifications: notifications, for: user)
|
||||
with {:ok, _} <- Notification.set_read_up_to(user, max_id) do
|
||||
conn
|
||||
|> json("ok")
|
||||
else
|
||||
{:error, message} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{"error" => message})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -646,6 +646,7 @@ defmodule Pleroma.Web.Router do
|
|||
patch("/accounts/update_credentials", AccountController, :update_credentials)
|
||||
|
||||
get("/accounts/relationships", AccountController, :relationships)
|
||||
get("/accounts/familiar_followers", AccountController, :familiar_followers)
|
||||
get("/accounts/:id/lists", AccountController, :lists)
|
||||
get("/accounts/:id/identity_proofs", AccountController, :identity_proofs)
|
||||
get("/endorsements", AccountController, :endorsements)
|
||||
|
|
|
@ -449,9 +449,7 @@ test "it sets all notifications as read up to a specified notification ID" do
|
|||
status: "hey yet again @#{other_user.nickname}!"
|
||||
})
|
||||
|
||||
[_, read_notification] = Notification.set_read_up_to(other_user, n2.id)
|
||||
|
||||
assert read_notification.activity.object
|
||||
Notification.set_read_up_to(other_user, n2.id)
|
||||
|
||||
[n3, n2, n1] = Notification.for_user(other_user)
|
||||
|
||||
|
|
|
@ -2894,6 +2894,20 @@ test "should report error on non-existing alias" do
|
|||
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
|
||||
test "it pins people" do
|
||||
user = insert(:user)
|
||||
|
|
|
@ -2172,6 +2172,55 @@ test "max pinned accounts", %{user: user, conn: conn} do
|
|||
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
|
||||
setup do: oauth_access(["follow"])
|
||||
|
||||
|
|
|
@ -21,13 +21,11 @@ test "it marks a single notification as read", %{user: user1, conn: conn} do
|
|||
{:ok, [notification1]} = Notification.create_notifications(activity1)
|
||||
{:ok, [notification2]} = Notification.create_notifications(activity2)
|
||||
|
||||
response =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/pleroma/notifications/read", %{id: notification1.id})
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
assert %{"pleroma" => %{"is_seen" => true}} = response
|
||||
assert Repo.get(Notification, notification1.id).seen
|
||||
refute Repo.get(Notification, notification2.id).seen
|
||||
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})
|
||||
|
||||
[response1, response2] =
|
||||
refute Repo.get(Notification, notification1.id).seen
|
||||
refute Repo.get(Notification, notification2.id).seen
|
||||
refute Repo.get(Notification, notification3.id).seen
|
||||
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/pleroma/notifications/read", %{max_id: notification2.id})
|
||||
|> json_response_and_validate_schema(:ok)
|
||||
|
||||
assert %{"pleroma" => %{"is_seen" => true}} = response1
|
||||
assert %{"pleroma" => %{"is_seen" => true}} = response2
|
||||
[notification3, notification2, notification1] = Notification.for_user(user1, %{limit: 3})
|
||||
|
||||
assert Repo.get(Notification, notification1.id).seen
|
||||
assert Repo.get(Notification, notification2.id).seen
|
||||
refute Repo.get(Notification, notification3.id).seen
|
||||
|
|
Loading…
Reference in New Issue