Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85806466
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
7 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/changelog.d/scrobble-type-filter.fix b/changelog.d/scrobble-type-filter.fix
new file mode 100644
index 000000000..f84f8fe4a
--- /dev/null
+++ b/changelog.d/scrobble-type-filter.fix
@@ -0,0 +1 @@
+Use a scalar activity-type filter for scrobble lookups so PostgreSQL can use the existing actor/type index for latest results.
diff --git a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex
index 207446a84..61e71c9e1 100644
--- a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex
@@ -1,56 +1,56 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.ScrobbleController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(
OAuthScopesPlug,
%{scopes: ["read:scrobbles"], fallback: :proceed_unauthenticated} when action == :index
)
plug(OAuthScopesPlug, %{scopes: ["write:scrobbles"]} when action == :create)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaScrobbleOperation
def create(%{assigns: %{user: user}, body_params: params} = conn, _) do
params =
params
|> Map.put_new(:external_link, Map.get(params, :externalLink))
with {:ok, activity} <- CommonAPI.listen(user, params) do
render(conn, "show.json", activity: activity, for: user)
else
{:error, message} ->
conn
|> put_status(:bad_request)
|> json(%{"error" => message})
end
end
def index(%{assigns: %{user: reading_user}} = conn, %{id: id} = params) do
with %User{} = user <- User.get_cached_by_nickname_or_id(id, for: reading_user) do
- params = Map.put(params, :type, ["Listen"])
+ params = Map.put(params, :type, "Listen")
activities = ActivityPub.fetch_user_abstract_activities(user, reading_user, params)
conn
|> add_link_headers(activities)
|> render("index.json", %{
activities: activities,
for: reading_user,
as: :activity
})
end
end
end
diff --git a/test/pleroma/web/pleroma_api/controllers/scrobble_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/scrobble_controller_test.exs
index fd405d7d7..06ac89a0f 100644
--- a/test/pleroma/web/pleroma_api/controllers/scrobble_controller_test.exs
+++ b/test/pleroma/web/pleroma_api/controllers/scrobble_controller_test.exs
@@ -1,87 +1,135 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.ScrobbleControllerTest do
use Pleroma.Web.ConnCase, async: true
alias Pleroma.Web.CommonAPI
+ import Pleroma.Factory
+
describe "POST /api/v1/pleroma/scrobble" do
test "works correctly" do
%{conn: conn} = oauth_access(["write:scrobbles"])
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/pleroma/scrobble", %{
"title" => "lain radio episode 1",
"artist" => "lain",
"album" => "lain radio",
"length" => "180000",
"external_link" => "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+1"
})
assert %{
"title" => "lain radio episode 1",
"external_link" => "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+1"
} = json_response_and_validate_schema(conn, 200)
end
test "external_link fallback" do
%{conn: conn} = oauth_access(["write"])
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/pleroma/scrobble", %{
"title" => "lain radio episode 2",
"artist" => "lain",
"album" => "lain radio",
"length" => "180000",
"externalLink" => "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+2"
})
assert %{
"title" => "lain radio episode 2",
"external_link" => "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+2"
} = json_response_and_validate_schema(conn, 200)
end
end
describe "GET /api/v1/pleroma/accounts/:id/scrobbles" do
test "works correctly" do
%{user: user, conn: conn} = oauth_access(["read:scrobbles"])
{:ok, _activity} =
CommonAPI.listen(user, %{
title: "lain radio episode 1",
artist: "lain",
album: "lain radio",
external_link: "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+1"
})
{:ok, _activity} =
CommonAPI.listen(user, %{
title: "lain radio episode 2",
artist: "lain",
album: "lain radio",
external_link: "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+2"
})
{:ok, _activity} =
CommonAPI.listen(user, %{
title: "lain radio episode 3",
artist: "lain",
album: "lain radio",
external_link: "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+3"
})
conn = get(conn, "/api/v1/pleroma/accounts/#{user.id}/scrobbles")
result = json_response_and_validate_schema(conn, 200)
assert length(result) == 3
end
+
+ test "uses a scalar type predicate when an account has no scrobbles" do
+ user = insert(:user)
+ {:ok, _} = CommonAPI.post(user, %{status: "A regular post, not a listen"})
+ handler_id = {__MODULE__, make_ref()}
+
+ :ok =
+ :telemetry.attach(
+ handler_id,
+ [:pleroma, :repo, :query],
+ &__MODULE__.capture_activity_query/4,
+ self()
+ )
+
+ on_exit(fn -> :telemetry.detach(handler_id) end)
+
+ conn = get(build_conn(), "/api/v1/pleroma/accounts/#{user.id}/scrobbles", %{limit: 1})
+
+ assert json_response_and_validate_schema(conn, 200) == []
+ assert_receive {:activity_query, query, params}
+ assert query =~ ~r/->>'type' = \$\d+/
+ refute query =~ ~r/->>'type' = ANY\(/
+ assert "Listen" in params
+ end
+
+ test "returns only the latest account scrobble amid unrelated activities" do
+ user = insert(:user)
+ attrs = %{artist: "lain", album: "radio", external_link: "https://example.com/listen"}
+
+ for title <- ["first listen", "latest listen"] do
+ {:ok, _} = CommonAPI.listen(user, Map.put(attrs, :title, title))
+ end
+
+ {:ok, _} = CommonAPI.post(user, %{status: "A newer regular post"})
+ {:ok, _} = CommonAPI.listen(insert(:user), Map.put(attrs, :title, "foreign listen"))
+
+ conn = get(build_conn(), "/api/v1/pleroma/accounts/#{user.id}/scrobbles", %{limit: 1})
+
+ assert [%{"title" => "latest listen"}] = json_response_and_validate_schema(conn, 200)
+ end
+ end
+
+ def capture_activity_query(_event, _measurements, %{query: query, params: params}, pid) do
+ if self() == pid and String.contains?(query, ~s(FROM "activities")) do
+ send(pid, {:activity_query, query, params})
+ end
end
end
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Tue, Oct 13, 12:50 AM (1 d, 22 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1786536
Default Alt Text
(7 KB)
Attached To
Mode
rPUBE pleroma-upstream
Attached
Detach File
Event Timeline
Log In to Comment