mirror of
https://akkoma.dev/AkkomaGang/akkoma.git
synced 2024-12-25 04:53:06 +00:00
remove all endpoints marked as deprecated (#91)
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma/pulls/91
This commit is contained in:
parent
ffc5944334
commit
dc9f66749c
|
@ -8,6 +8,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- Scrobbling support
|
- Scrobbling support
|
||||||
|
- `/api/v1/pleroma/scrobble`
|
||||||
|
- `/api/v1/pleroma/accounts/{id}/scrobbles`
|
||||||
|
- Deprecated endpoints
|
||||||
|
- `/api/v1/pleroma/chats`
|
||||||
|
- `/api/v1/notifications/dismiss`
|
||||||
|
- `/api/v1/search`
|
||||||
|
- `/api/v1/statuses/{id}/card`
|
||||||
|
|
||||||
## 2022.07
|
## 2022.07
|
||||||
|
|
||||||
|
|
|
@ -194,17 +194,6 @@ defmodule Pleroma.User do
|
||||||
has_many(incoming_relation_source, through: [incoming_relation, :source])
|
has_many(incoming_relation_source, through: [incoming_relation, :source])
|
||||||
end
|
end
|
||||||
|
|
||||||
# `:blocks` is deprecated (replaced with `blocked_users` relation)
|
|
||||||
field(:blocks, {:array, :string}, default: [])
|
|
||||||
# `:mutes` is deprecated (replaced with `muted_users` relation)
|
|
||||||
field(:mutes, {:array, :string}, default: [])
|
|
||||||
# `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
|
|
||||||
field(:muted_reblogs, {:array, :string}, default: [])
|
|
||||||
# `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
|
|
||||||
field(:muted_notifications, {:array, :string}, default: [])
|
|
||||||
# `:subscribers` is deprecated (replaced with `subscriber_users` relation)
|
|
||||||
field(:subscribers, {:array, :string}, default: [])
|
|
||||||
|
|
||||||
embeds_one(
|
embeds_one(
|
||||||
:multi_factor_authentication_settings,
|
:multi_factor_authentication_settings,
|
||||||
MFA.Settings,
|
MFA.Settings,
|
||||||
|
|
|
@ -128,28 +128,6 @@ def create_operation do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def index_operation do
|
|
||||||
%Operation{
|
|
||||||
tags: ["Chats"],
|
|
||||||
summary: "Retrieve list of chats (unpaginated)",
|
|
||||||
deprecated: true,
|
|
||||||
description:
|
|
||||||
"Deprecated due to no support for pagination. Using [/api/v2/pleroma/chats](#operation/ChatController.index2) instead is recommended.",
|
|
||||||
operationId: "ChatController.index",
|
|
||||||
parameters: [
|
|
||||||
Operation.parameter(:with_muted, :query, BooleanLike, "Include chats from muted users")
|
|
||||||
],
|
|
||||||
responses: %{
|
|
||||||
200 => Operation.response("The chats of the user", "application/json", chats_response())
|
|
||||||
},
|
|
||||||
security: [
|
|
||||||
%{
|
|
||||||
"oAuth" => ["read:chats"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def index2_operation do
|
def index2_operation do
|
||||||
%Operation{
|
%Operation{
|
||||||
tags: ["Chats"],
|
tags: ["Chats"],
|
||||||
|
|
|
@ -108,24 +108,6 @@ def dismiss_operation do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def dismiss_via_body_operation do
|
|
||||||
%Operation{
|
|
||||||
tags: ["Notifications"],
|
|
||||||
summary: "Dismiss a single notification",
|
|
||||||
deprecated: true,
|
|
||||||
description: "Clear a single notification from the server.",
|
|
||||||
operationId: "NotificationController.dismiss_via_body",
|
|
||||||
requestBody:
|
|
||||||
request_body(
|
|
||||||
"Parameters",
|
|
||||||
%Schema{type: :object, properties: %{id: %Schema{type: :string}}},
|
|
||||||
required: true
|
|
||||||
),
|
|
||||||
security: [%{"oAuth" => ["write:notifications"]}],
|
|
||||||
responses: %{200 => empty_object_response()}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy_multiple_operation do
|
def destroy_multiple_operation do
|
||||||
%Operation{
|
%Operation{
|
||||||
tags: ["Notifications"],
|
tags: ["Notifications"],
|
||||||
|
|
|
@ -59,53 +59,6 @@ def account_search_operation do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def search_operation do
|
|
||||||
%Operation{
|
|
||||||
tags: ["Search"],
|
|
||||||
summary: "Search results",
|
|
||||||
security: [%{"oAuth" => ["read:search"]}],
|
|
||||||
operationId: "SearchController.search",
|
|
||||||
deprecated: true,
|
|
||||||
parameters: [
|
|
||||||
Operation.parameter(
|
|
||||||
:account_id,
|
|
||||||
:query,
|
|
||||||
FlakeID,
|
|
||||||
"If provided, statuses returned will be authored only by this account"
|
|
||||||
),
|
|
||||||
Operation.parameter(
|
|
||||||
:type,
|
|
||||||
:query,
|
|
||||||
%Schema{type: :string, enum: ["accounts", "hashtags", "statuses"]},
|
|
||||||
"Search type"
|
|
||||||
),
|
|
||||||
Operation.parameter(:q, :query, %Schema{type: :string}, "The search query", required: true),
|
|
||||||
Operation.parameter(
|
|
||||||
:resolve,
|
|
||||||
:query,
|
|
||||||
%Schema{allOf: [BooleanLike], default: false},
|
|
||||||
"Attempt WebFinger lookup"
|
|
||||||
),
|
|
||||||
Operation.parameter(
|
|
||||||
:following,
|
|
||||||
:query,
|
|
||||||
%Schema{allOf: [BooleanLike], default: false},
|
|
||||||
"Only include accounts that the user is following"
|
|
||||||
),
|
|
||||||
Operation.parameter(
|
|
||||||
:offset,
|
|
||||||
:query,
|
|
||||||
%Schema{type: :integer},
|
|
||||||
"Offset"
|
|
||||||
),
|
|
||||||
with_relationships_param() | pagination_params()
|
|
||||||
],
|
|
||||||
responses: %{
|
|
||||||
200 => Operation.response("Results", "application/json", results())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def search2_operation do
|
def search2_operation do
|
||||||
%Operation{
|
%Operation{
|
||||||
tags: ["Search"],
|
tags: ["Search"],
|
||||||
|
@ -176,33 +129,4 @@ defp results2 do
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp results do
|
|
||||||
%Schema{
|
|
||||||
title: "SearchResults",
|
|
||||||
type: :object,
|
|
||||||
properties: %{
|
|
||||||
accounts: %Schema{
|
|
||||||
type: :array,
|
|
||||||
items: Account,
|
|
||||||
description: "Accounts which match the given query"
|
|
||||||
},
|
|
||||||
statuses: %Schema{
|
|
||||||
type: :array,
|
|
||||||
items: Status,
|
|
||||||
description: "Statuses which match the given query"
|
|
||||||
},
|
|
||||||
hashtags: %Schema{
|
|
||||||
type: :array,
|
|
||||||
items: %Schema{type: :string},
|
|
||||||
description: "Hashtags which match the given query"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
example: %{
|
|
||||||
"accounts" => [Account.schema().example],
|
|
||||||
"statuses" => [Status.schema().example],
|
|
||||||
"hashtags" => ["cofe"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -323,34 +323,6 @@ def unmute_conversation_operation do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def card_operation do
|
|
||||||
%Operation{
|
|
||||||
tags: ["Retrieve status information"],
|
|
||||||
deprecated: true,
|
|
||||||
summary: "Preview card",
|
|
||||||
description: "Deprecated in favor of card property inlined on Status entity",
|
|
||||||
operationId: "StatusController.card",
|
|
||||||
parameters: [id_param()],
|
|
||||||
security: [%{"oAuth" => ["read:statuses"]}],
|
|
||||||
responses: %{
|
|
||||||
200 =>
|
|
||||||
Operation.response("Card", "application/json", %Schema{
|
|
||||||
type: :object,
|
|
||||||
nullable: true,
|
|
||||||
properties: %{
|
|
||||||
type: %Schema{type: :string, enum: ["link", "photo", "video", "rich"]},
|
|
||||||
provider_name: %Schema{type: :string, nullable: true},
|
|
||||||
provider_url: %Schema{type: :string, format: :uri},
|
|
||||||
url: %Schema{type: :string, format: :uri},
|
|
||||||
image: %Schema{type: :string, nullable: true, format: :uri},
|
|
||||||
title: %Schema{type: :string},
|
|
||||||
description: %Schema{type: :string}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def favourited_by_operation do
|
def favourited_by_operation do
|
||||||
%Operation{
|
%Operation{
|
||||||
tags: ["Retrieve status information"],
|
tags: ["Retrieve status information"],
|
||||||
|
|
|
@ -30,7 +30,7 @@ def handle_error(plug, error),
|
||||||
def auth_template do
|
def auth_template do
|
||||||
# Note: `config :pleroma, :auth_template, "..."` support is deprecated
|
# Note: `config :pleroma, :auth_template, "..."` support is deprecated
|
||||||
implementation().auth_template() ||
|
implementation().auth_template() ||
|
||||||
Pleroma.Config.get([:auth, :auth_template], Pleroma.Config.get(:auth_template)) ||
|
Pleroma.Config.get(:auth_template) ||
|
||||||
"show.html"
|
"show.html"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -98,11 +98,6 @@ def dismiss(%{assigns: %{user: user}} = conn, %{id: id} = _params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# POST /api/v1/notifications/dismiss (deprecated)
|
|
||||||
def dismiss_via_body(%{body_params: params} = conn, _) do
|
|
||||||
dismiss(conn, params)
|
|
||||||
end
|
|
||||||
|
|
||||||
# DELETE /api/v1/notifications/destroy_multiple
|
# DELETE /api/v1/notifications/destroy_multiple
|
||||||
def destroy_multiple(%{assigns: %{user: user}} = conn, %{ids: ids} = _params) do
|
def destroy_multiple(%{assigns: %{user: user}} = conn, %{ids: ids} = _params) do
|
||||||
Notification.destroy_multiple(user, ids)
|
Notification.destroy_multiple(user, ids)
|
||||||
|
|
|
@ -25,7 +25,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
|
||||||
|
|
||||||
# Note: on private instances auth is required (EnsurePublicOrAuthenticatedPlug is not skipped)
|
# Note: on private instances auth is required (EnsurePublicOrAuthenticatedPlug is not skipped)
|
||||||
|
|
||||||
plug(RateLimiter, [name: :search] when action in [:search, :search2, :account_search])
|
plug(RateLimiter, [name: :search] when action in [:search2, :account_search])
|
||||||
|
|
||||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.SearchOperation
|
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.SearchOperation
|
||||||
|
|
||||||
|
@ -42,7 +42,6 @@ def account_search(%{assigns: %{user: user}} = conn, %{q: query} = params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def search2(conn, params), do: do_search(:v2, conn, params)
|
def search2(conn, params), do: do_search(:v2, conn, params)
|
||||||
def search(conn, params), do: do_search(:v1, conn, params)
|
|
||||||
|
|
||||||
defp do_search(version, %{assigns: %{user: user}} = conn, %{q: query} = params) do
|
defp do_search(version, %{assigns: %{user: user}} = conn, %{q: query} = params) do
|
||||||
query = String.trim(query)
|
query = String.trim(query)
|
||||||
|
@ -118,10 +117,6 @@ defp resource_search(:v2, "hashtags", query, options) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp resource_search(:v1, "hashtags", query, options) do
|
|
||||||
prepare_tags(query, options)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp prepare_tags(query, options) do
|
defp prepare_tags(query, options) do
|
||||||
tags =
|
tags =
|
||||||
query
|
query
|
||||||
|
|
|
@ -37,7 +37,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
||||||
when action in [
|
when action in [
|
||||||
:index,
|
:index,
|
||||||
:show,
|
:show,
|
||||||
:card,
|
|
||||||
:context
|
:context
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -315,18 +314,6 @@ def unmute_conversation(%{assigns: %{user: user}} = conn, %{id: id}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "GET /api/v1/statuses/:id/card"
|
|
||||||
@deprecated "https://github.com/tootsuite/mastodon/pull/11213"
|
|
||||||
def card(%{assigns: %{user: user}} = conn, %{id: status_id}) do
|
|
||||||
with %Activity{} = activity <- Activity.get_by_id(status_id),
|
|
||||||
true <- Visibility.visible_for_user?(activity, user) do
|
|
||||||
data = Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
|
|
||||||
render(conn, "card.json", data)
|
|
||||||
else
|
|
||||||
_ -> render_error(conn, :not_found, "Record not found")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc "GET /api/v1/statuses/:id/favourited_by"
|
@doc "GET /api/v1/statuses/:id/favourited_by"
|
||||||
def favourited_by(%{assigns: %{user: user}} = conn, %{id: id}) do
|
def favourited_by(%{assigns: %{user: user}} = conn, %{id: id}) do
|
||||||
with true <- Pleroma.Config.get([:instance, :show_reactions]),
|
with true <- Pleroma.Config.get([:instance, :show_reactions]),
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Web.RichMedia.Parsers.OGP do
|
|
||||||
@deprecated "OGP parser is deprecated. Use TwitterCard instead."
|
|
||||||
def parse(_html, _data) do
|
|
||||||
%{}
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -428,7 +428,6 @@ defmodule Pleroma.Web.Router do
|
||||||
pipe_through(:authenticated_api)
|
pipe_through(:authenticated_api)
|
||||||
|
|
||||||
post("/chats/by-account-id/:id", ChatController, :create)
|
post("/chats/by-account-id/:id", ChatController, :create)
|
||||||
get("/chats", ChatController, :index)
|
|
||||||
get("/chats/:id", ChatController, :show)
|
get("/chats/:id", ChatController, :show)
|
||||||
get("/chats/:id/messages", ChatController, :messages)
|
get("/chats/:id/messages", ChatController, :messages)
|
||||||
post("/chats/:id/messages", ChatController, :post_chat_message)
|
post("/chats/:id/messages", ChatController, :post_chat_message)
|
||||||
|
@ -543,8 +542,6 @@ defmodule Pleroma.Web.Router do
|
||||||
post("/notifications/:id/dismiss", NotificationController, :dismiss)
|
post("/notifications/:id/dismiss", NotificationController, :dismiss)
|
||||||
post("/notifications/clear", NotificationController, :clear)
|
post("/notifications/clear", NotificationController, :clear)
|
||||||
delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple)
|
delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple)
|
||||||
# Deprecated: was removed in Mastodon v3, use `/notifications/:id/dismiss` instead
|
|
||||||
post("/notifications/dismiss", NotificationController, :dismiss_via_body)
|
|
||||||
|
|
||||||
post("/polls/:id/votes", PollController, :vote)
|
post("/polls/:id/votes", PollController, :vote)
|
||||||
|
|
||||||
|
@ -607,8 +604,6 @@ defmodule Pleroma.Web.Router do
|
||||||
pipe_through(:api)
|
pipe_through(:api)
|
||||||
|
|
||||||
get("/accounts/search", SearchController, :account_search)
|
get("/accounts/search", SearchController, :account_search)
|
||||||
get("/search", SearchController, :search)
|
|
||||||
|
|
||||||
get("/accounts/lookup", AccountController, :lookup)
|
get("/accounts/lookup", AccountController, :lookup)
|
||||||
|
|
||||||
get("/accounts/:id/statuses", AccountController, :statuses)
|
get("/accounts/:id/statuses", AccountController, :statuses)
|
||||||
|
@ -624,7 +619,6 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/statuses", StatusController, :index)
|
get("/statuses", StatusController, :index)
|
||||||
get("/statuses/:id", StatusController, :show)
|
get("/statuses/:id", StatusController, :show)
|
||||||
get("/statuses/:id/context", StatusController, :context)
|
get("/statuses/:id/context", StatusController, :context)
|
||||||
get("/statuses/:id/card", StatusController, :card)
|
|
||||||
get("/statuses/:id/favourited_by", StatusController, :favourited_by)
|
get("/statuses/:id/favourited_by", StatusController, :favourited_by)
|
||||||
get("/statuses/:id/reblogged_by", StatusController, :reblogged_by)
|
get("/statuses/:id/reblogged_by", StatusController, :reblogged_by)
|
||||||
|
|
||||||
|
|
|
@ -137,23 +137,6 @@ test "getting a single notification" do
|
||||||
assert response == expected_response
|
assert response == expected_response
|
||||||
end
|
end
|
||||||
|
|
||||||
test "dismissing a single notification (deprecated endpoint)" do
|
|
||||||
%{user: user, conn: conn} = oauth_access(["write:notifications"])
|
|
||||||
other_user = insert(:user)
|
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
|
|
||||||
|
|
||||||
{:ok, [notification]} = Notification.create_notifications(activity)
|
|
||||||
|
|
||||||
conn =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> put_req_header("content-type", "application/json")
|
|
||||||
|> post("/api/v1/notifications/dismiss", %{"id" => to_string(notification.id)})
|
|
||||||
|
|
||||||
assert %{} = json_response_and_validate_schema(conn, 200)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "dismissing a single notification" do
|
test "dismissing a single notification" do
|
||||||
%{user: user, conn: conn} = oauth_access(["write:notifications"])
|
%{user: user, conn: conn} = oauth_access(["write:notifications"])
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
|
defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do
|
||||||
use Pleroma.Web.ConnCase
|
use Pleroma.Web.ConnCase
|
||||||
|
|
||||||
alias Pleroma.Object
|
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.Endpoint
|
alias Pleroma.Web.Endpoint
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
@ -221,189 +220,4 @@ test "returns account if query contains a space", %{conn: conn} do
|
||||||
assert length(results) == 1
|
assert length(results) == 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe ".search" do
|
|
||||||
test "it returns empty result if user or status search return undefined error", %{conn: conn} do
|
|
||||||
with_mocks [
|
|
||||||
{Pleroma.User, [], [search: fn _q, _o -> raise "Oops" end]},
|
|
||||||
{Pleroma.Activity, [], [search: fn _u, _q, _o -> raise "Oops" end]}
|
|
||||||
] do
|
|
||||||
capture_log(fn ->
|
|
||||||
results =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/search?q=2hu")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert results["accounts"] == []
|
|
||||||
assert results["statuses"] == []
|
|
||||||
end) =~
|
|
||||||
"[error] Elixir.Pleroma.Web.MastodonAPI.SearchController search error: %RuntimeError{message: \"Oops\"}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "search", %{conn: conn} do
|
|
||||||
user = insert(:user)
|
|
||||||
user_two = insert(:user, %{nickname: "shp@shitposter.club"})
|
|
||||||
user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
|
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "This is about 2hu"})
|
|
||||||
|
|
||||||
{:ok, _activity} =
|
|
||||||
CommonAPI.post(user, %{
|
|
||||||
status: "This is about 2hu, but private",
|
|
||||||
visibility: "private"
|
|
||||||
})
|
|
||||||
|
|
||||||
{:ok, _} = CommonAPI.post(user_two, %{status: "This isn't"})
|
|
||||||
|
|
||||||
results =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/search?q=2hu")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
[account | _] = results["accounts"]
|
|
||||||
assert account["id"] == to_string(user_three.id)
|
|
||||||
|
|
||||||
assert results["hashtags"] == ["2hu"]
|
|
||||||
|
|
||||||
[status] = results["statuses"]
|
|
||||||
assert status["id"] == to_string(activity.id)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "search fetches remote statuses and prefers them over other results", %{conn: conn} do
|
|
||||||
old_version = :persistent_term.get({Pleroma.Repo, :postgres_version})
|
|
||||||
:persistent_term.put({Pleroma.Repo, :postgres_version}, 10.0)
|
|
||||||
on_exit(fn -> :persistent_term.put({Pleroma.Repo, :postgres_version}, old_version) end)
|
|
||||||
|
|
||||||
capture_log(fn ->
|
|
||||||
{:ok, %{id: activity_id}} =
|
|
||||||
CommonAPI.post(insert(:user), %{
|
|
||||||
status: "check out http://mastodon.example.org/@admin/99541947525187367"
|
|
||||||
})
|
|
||||||
|
|
||||||
results =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/search?q=http://mastodon.example.org/@admin/99541947525187367")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert [
|
|
||||||
%{"url" => "http://mastodon.example.org/@admin/99541947525187367"},
|
|
||||||
%{"id" => ^activity_id}
|
|
||||||
] = results["statuses"]
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "search doesn't show statuses that it shouldn't", %{conn: conn} do
|
|
||||||
{:ok, activity} =
|
|
||||||
CommonAPI.post(insert(:user), %{
|
|
||||||
status: "This is about 2hu, but private",
|
|
||||||
visibility: "private"
|
|
||||||
})
|
|
||||||
|
|
||||||
capture_log(fn ->
|
|
||||||
q = Object.normalize(activity, fetch: false).data["id"]
|
|
||||||
|
|
||||||
results =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/search?q=#{q}")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
[] = results["statuses"]
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "search fetches remote accounts", %{conn: conn} do
|
|
||||||
user = insert(:user)
|
|
||||||
|
|
||||||
query = URI.encode_query(%{q: " mike@osada.macgirvin.com ", resolve: true})
|
|
||||||
|
|
||||||
results =
|
|
||||||
conn
|
|
||||||
|> assign(:user, user)
|
|
||||||
|> assign(:token, insert(:oauth_token, user: user, scopes: ["read"]))
|
|
||||||
|> get("/api/v1/search?#{query}")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
[account] = results["accounts"]
|
|
||||||
assert account["acct"] == "mike@osada.macgirvin.com"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "search doesn't fetch remote accounts if resolve is false", %{conn: conn} do
|
|
||||||
results =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/search?q=mike@osada.macgirvin.com&resolve=false")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert [] == results["accounts"]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "search with limit and offset", %{conn: conn} do
|
|
||||||
user = insert(:user)
|
|
||||||
_user_two = insert(:user, %{nickname: "shp@shitposter.club"})
|
|
||||||
_user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
|
|
||||||
|
|
||||||
{:ok, _activity1} = CommonAPI.post(user, %{status: "This is about 2hu"})
|
|
||||||
{:ok, _activity2} = CommonAPI.post(user, %{status: "This is also about 2hu"})
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/search?q=2hu&limit=1")
|
|
||||||
|
|
||||||
assert results = json_response_and_validate_schema(result, 200)
|
|
||||||
assert [%{"id" => activity_id1}] = results["statuses"]
|
|
||||||
assert [_] = results["accounts"]
|
|
||||||
|
|
||||||
results =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/search?q=2hu&limit=1&offset=1")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert [%{"id" => activity_id2}] = results["statuses"]
|
|
||||||
assert [] = results["accounts"]
|
|
||||||
|
|
||||||
assert activity_id1 != activity_id2
|
|
||||||
end
|
|
||||||
|
|
||||||
test "search returns results only for the given type", %{conn: conn} do
|
|
||||||
user = insert(:user)
|
|
||||||
_user_two = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
|
|
||||||
|
|
||||||
{:ok, _activity} = CommonAPI.post(user, %{status: "This is about 2hu"})
|
|
||||||
|
|
||||||
assert %{"statuses" => [_activity], "accounts" => [], "hashtags" => []} =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/search?q=2hu&type=statuses")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert %{"statuses" => [], "accounts" => [_user_two], "hashtags" => []} =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/search?q=2hu&type=accounts")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "search uses account_id to filter statuses by the author", %{conn: conn} do
|
|
||||||
user = insert(:user, %{nickname: "shp@shitposter.club"})
|
|
||||||
user_two = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
|
|
||||||
|
|
||||||
{:ok, activity1} = CommonAPI.post(user, %{status: "This is about 2hu"})
|
|
||||||
{:ok, activity2} = CommonAPI.post(user_two, %{status: "This is also about 2hu"})
|
|
||||||
|
|
||||||
results =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/search?q=2hu&account_id=#{user.id}")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert [%{"id" => activity_id1}] = results["statuses"]
|
|
||||||
assert activity_id1 == activity1.id
|
|
||||||
assert [_] = results["accounts"]
|
|
||||||
|
|
||||||
results =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/search?q=2hu&account_id=#{user_two.id}")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert [%{"id" => activity_id2}] = results["statuses"]
|
|
||||||
assert activity_id2 == activity2.id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1349,87 +1349,6 @@ test "on pin removes deletion job, on unpin reschedule deletion" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "cards" do
|
|
||||||
setup do
|
|
||||||
clear_config([:rich_media, :enabled], true)
|
|
||||||
|
|
||||||
oauth_access(["read:statuses"])
|
|
||||||
end
|
|
||||||
|
|
||||||
test "returns rich-media card", %{conn: conn, user: user} do
|
|
||||||
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "https://example.com/ogp"})
|
|
||||||
|
|
||||||
card_data = %{
|
|
||||||
"image" => "http://ia.media-imdb.com/images/rock.jpg",
|
|
||||||
"provider_name" => "example.com",
|
|
||||||
"provider_url" => "https://example.com",
|
|
||||||
"title" => "The Rock",
|
|
||||||
"type" => "link",
|
|
||||||
"url" => "https://example.com/ogp",
|
|
||||||
"description" =>
|
|
||||||
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
|
|
||||||
"pleroma" => %{
|
|
||||||
"opengraph" => %{
|
|
||||||
"image" => "http://ia.media-imdb.com/images/rock.jpg",
|
|
||||||
"title" => "The Rock",
|
|
||||||
"type" => "video.movie",
|
|
||||||
"url" => "https://example.com/ogp",
|
|
||||||
"description" =>
|
|
||||||
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
response =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/statuses/#{activity.id}/card")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert response == card_data
|
|
||||||
|
|
||||||
# works with private posts
|
|
||||||
{:ok, activity} =
|
|
||||||
CommonAPI.post(user, %{status: "https://example.com/ogp", visibility: "direct"})
|
|
||||||
|
|
||||||
response_two =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/statuses/#{activity.id}/card")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert response_two == card_data
|
|
||||||
end
|
|
||||||
|
|
||||||
test "replaces missing description with an empty string", %{conn: conn, user: user} do
|
|
||||||
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "https://example.com/ogp-missing-data"})
|
|
||||||
|
|
||||||
response =
|
|
||||||
conn
|
|
||||||
|> get("/api/v1/statuses/#{activity.id}/card")
|
|
||||||
|> json_response_and_validate_schema(:ok)
|
|
||||||
|
|
||||||
assert response == %{
|
|
||||||
"type" => "link",
|
|
||||||
"title" => "Pleroma",
|
|
||||||
"description" => "",
|
|
||||||
"image" => nil,
|
|
||||||
"provider_name" => "example.com",
|
|
||||||
"provider_url" => "https://example.com",
|
|
||||||
"url" => "https://example.com/ogp-missing-data",
|
|
||||||
"pleroma" => %{
|
|
||||||
"opengraph" => %{
|
|
||||||
"title" => "Pleroma",
|
|
||||||
"type" => "website",
|
|
||||||
"url" => "https://example.com/ogp-missing-data"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "bookmarks" do
|
test "bookmarks" do
|
||||||
bookmarks_uri = "/api/v1/bookmarks"
|
bookmarks_uri = "/api/v1/bookmarks"
|
||||||
|
|
||||||
|
|
|
@ -307,165 +307,147 @@ test "it returns a chat", %{conn: conn, user: user} do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for tested_endpoint <- ["/api/v1/pleroma/chats", "/api/v2/pleroma/chats"] do
|
describe "GET /api/v2/pleroma/chats" do
|
||||||
describe "GET #{tested_endpoint}" do
|
setup do: oauth_access(["read:chats"])
|
||||||
setup do: oauth_access(["read:chats"])
|
|
||||||
|
|
||||||
test "it does not return chats with deleted users", %{conn: conn, user: user} do
|
test "it does not return chats with deleted users", %{conn: conn, user: user} do
|
||||||
|
recipient = insert(:user)
|
||||||
|
{:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
|
||||||
|
|
||||||
|
Pleroma.Repo.delete(recipient)
|
||||||
|
User.invalidate_cache(recipient)
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/v2/pleroma/chats")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert length(result) == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it does not return chats with users you blocked", %{conn: conn, user: user} do
|
||||||
|
recipient = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/v2/pleroma/chats")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert length(result) == 1
|
||||||
|
|
||||||
|
User.block(user, recipient)
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/v2/pleroma/chats")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert length(result) == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it does not return chats with users you muted", %{conn: conn, user: user} do
|
||||||
|
recipient = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/v2/pleroma/chats")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert length(result) == 1
|
||||||
|
|
||||||
|
User.mute(user, recipient)
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/v2/pleroma/chats")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert length(result) == 0
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/v2/pleroma/chats?with_muted=true")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert length(result) == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it paginates chats", %{conn: conn, user: user} do
|
||||||
|
Enum.each(1..30, fn _ ->
|
||||||
recipient = insert(:user)
|
recipient = insert(:user)
|
||||||
{:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
|
{:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
|
||||||
|
end)
|
||||||
|
|
||||||
Pleroma.Repo.delete(recipient)
|
result =
|
||||||
User.invalidate_cache(recipient)
|
conn
|
||||||
|
|> get("/api/v2/pleroma/chats")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
result =
|
assert length(result) == 20
|
||||||
conn
|
last_id = List.last(result)["id"]
|
||||||
|> get(unquote(tested_endpoint))
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert length(result) == 0
|
result =
|
||||||
end
|
conn
|
||||||
|
|> get("/api/v2/pleroma/chats?max_id=#{last_id}")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
test "it does not return chats with users you blocked", %{conn: conn, user: user} do
|
assert length(result) == 10
|
||||||
recipient = insert(:user)
|
end
|
||||||
|
|
||||||
{:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
|
test "it return a list of chats the current user is participating in, in descending order of updates",
|
||||||
|
%{conn: conn, user: user} do
|
||||||
|
har = insert(:user)
|
||||||
|
jafnhar = insert(:user)
|
||||||
|
tridi = insert(:user)
|
||||||
|
|
||||||
result =
|
{:ok, chat_1} = Chat.get_or_create(user.id, har.ap_id)
|
||||||
conn
|
{:ok, chat_1} = time_travel(chat_1, -3)
|
||||||
|> get(unquote(tested_endpoint))
|
{:ok, chat_2} = Chat.get_or_create(user.id, jafnhar.ap_id)
|
||||||
|> json_response_and_validate_schema(200)
|
{:ok, _chat_2} = time_travel(chat_2, -2)
|
||||||
|
{:ok, chat_3} = Chat.get_or_create(user.id, tridi.ap_id)
|
||||||
|
{:ok, chat_3} = time_travel(chat_3, -1)
|
||||||
|
|
||||||
assert length(result) == 1
|
# bump the second one
|
||||||
|
{:ok, chat_2} = Chat.bump_or_create(user.id, jafnhar.ap_id)
|
||||||
|
|
||||||
User.block(user, recipient)
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/v2/pleroma/chats")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
result =
|
ids = Enum.map(result, & &1["id"])
|
||||||
conn
|
|
||||||
|> get(unquote(tested_endpoint))
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert length(result) == 0
|
assert ids == [
|
||||||
end
|
chat_2.id |> to_string(),
|
||||||
|
chat_3.id |> to_string(),
|
||||||
|
chat_1.id |> to_string()
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
test "it does not return chats with users you muted", %{conn: conn, user: user} do
|
test "it is not affected by :restrict_unauthenticated setting (issue #1973)", %{
|
||||||
recipient = insert(:user)
|
conn: conn,
|
||||||
|
user: user
|
||||||
|
} do
|
||||||
|
clear_config([:restrict_unauthenticated, :profiles, :local], true)
|
||||||
|
clear_config([:restrict_unauthenticated, :profiles, :remote], true)
|
||||||
|
|
||||||
{:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
|
user2 = insert(:user)
|
||||||
|
user3 = insert(:user, local: false)
|
||||||
|
|
||||||
result =
|
{:ok, _chat_12} = Chat.get_or_create(user.id, user2.ap_id)
|
||||||
conn
|
{:ok, _chat_13} = Chat.get_or_create(user.id, user3.ap_id)
|
||||||
|> get(unquote(tested_endpoint))
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert length(result) == 1
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/v2/pleroma/chats")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
User.mute(user, recipient)
|
account_ids = Enum.map(result, &get_in(&1, ["account", "id"]))
|
||||||
|
assert Enum.sort(account_ids) == Enum.sort([user2.id, user3.id])
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get(unquote(tested_endpoint))
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert length(result) == 0
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get("#{unquote(tested_endpoint)}?with_muted=true")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert length(result) == 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if tested_endpoint == "/api/v1/pleroma/chats" do
|
|
||||||
test "it returns all chats", %{conn: conn, user: user} do
|
|
||||||
Enum.each(1..30, fn _ ->
|
|
||||||
recipient = insert(:user)
|
|
||||||
{:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
|
|
||||||
end)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get(unquote(tested_endpoint))
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert length(result) == 30
|
|
||||||
end
|
|
||||||
else
|
|
||||||
test "it paginates chats", %{conn: conn, user: user} do
|
|
||||||
Enum.each(1..30, fn _ ->
|
|
||||||
recipient = insert(:user)
|
|
||||||
{:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
|
|
||||||
end)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get(unquote(tested_endpoint))
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert length(result) == 20
|
|
||||||
last_id = List.last(result)["id"]
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get(unquote(tested_endpoint) <> "?max_id=#{last_id}")
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
assert length(result) == 10
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it return a list of chats the current user is participating in, in descending order of updates",
|
|
||||||
%{conn: conn, user: user} do
|
|
||||||
har = insert(:user)
|
|
||||||
jafnhar = insert(:user)
|
|
||||||
tridi = insert(:user)
|
|
||||||
|
|
||||||
{:ok, chat_1} = Chat.get_or_create(user.id, har.ap_id)
|
|
||||||
{:ok, chat_1} = time_travel(chat_1, -3)
|
|
||||||
{:ok, chat_2} = Chat.get_or_create(user.id, jafnhar.ap_id)
|
|
||||||
{:ok, _chat_2} = time_travel(chat_2, -2)
|
|
||||||
{:ok, chat_3} = Chat.get_or_create(user.id, tridi.ap_id)
|
|
||||||
{:ok, chat_3} = time_travel(chat_3, -1)
|
|
||||||
|
|
||||||
# bump the second one
|
|
||||||
{:ok, chat_2} = Chat.bump_or_create(user.id, jafnhar.ap_id)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get(unquote(tested_endpoint))
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
ids = Enum.map(result, & &1["id"])
|
|
||||||
|
|
||||||
assert ids == [
|
|
||||||
chat_2.id |> to_string(),
|
|
||||||
chat_3.id |> to_string(),
|
|
||||||
chat_1.id |> to_string()
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it is not affected by :restrict_unauthenticated setting (issue #1973)", %{
|
|
||||||
conn: conn,
|
|
||||||
user: user
|
|
||||||
} do
|
|
||||||
clear_config([:restrict_unauthenticated, :profiles, :local], true)
|
|
||||||
clear_config([:restrict_unauthenticated, :profiles, :remote], true)
|
|
||||||
|
|
||||||
user2 = insert(:user)
|
|
||||||
user3 = insert(:user, local: false)
|
|
||||||
|
|
||||||
{:ok, _chat_12} = Chat.get_or_create(user.id, user2.ap_id)
|
|
||||||
{:ok, _chat_13} = Chat.get_or_create(user.id, user3.ap_id)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> get(unquote(tested_endpoint))
|
|
||||||
|> json_response_and_validate_schema(200)
|
|
||||||
|
|
||||||
account_ids = Enum.map(result, &get_in(&1, ["account", "id"]))
|
|
||||||
assert Enum.sort(account_ids) == Enum.sort([user2.id, user3.id])
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -42,7 +42,7 @@ defmacro clear_config(config_path, temp_setting) do
|
||||||
# Displaying a warning to prevent unintentional clearing of all but one keys in section
|
# Displaying a warning to prevent unintentional clearing of all but one keys in section
|
||||||
if Keyword.keyword?(temp_setting) and length(temp_setting) == 1 do
|
if Keyword.keyword?(temp_setting) and length(temp_setting) == 1 do
|
||||||
Logger.warn(
|
Logger.warn(
|
||||||
"Please change `clear_config([section], key: value)` to `clear_config([section, key], value)`"
|
"Please change `clear_config([section], key: value)` to `clear_config([section, key], value) (#{inspect(config_path)} = #{inspect(temp_setting)})`"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue