Page MenuHomePhorge

No OneTemporary

Size
7 KB
Referenced Files
None
Subscribers
None
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

Mime Type
text/x-diff
Expires
Tue, Oct 13, 12:50 AM (1 d, 21 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1786536
Default Alt Text
(7 KB)

Event Timeline