forked from AkkomaGang/akkoma
Merge branch 'openapi/pleroma-api/scrobble' into 'develop'
Add OpenAPI spec for PleromaAPI.ScrobbleController See merge request pleroma/pleroma!2559
This commit is contained in:
commit
5a149e5788
10 changed files with 206 additions and 78 deletions
|
@ -0,0 +1,102 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do
|
||||||
|
alias OpenApiSpex.Operation
|
||||||
|
alias OpenApiSpex.Reference
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.Account
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
|
||||||
|
|
||||||
|
import Pleroma.Web.ApiSpec.Helpers
|
||||||
|
|
||||||
|
def open_api_operation(action) do
|
||||||
|
operation = String.to_existing_atom("#{action}_operation")
|
||||||
|
apply(__MODULE__, operation, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Scrobbles"],
|
||||||
|
summary: "Creates a new Listen activity for an account",
|
||||||
|
security: [%{"oAuth" => ["write"]}],
|
||||||
|
operationId: "PleromaAPI.ScrobbleController.create",
|
||||||
|
requestBody: request_body("Parameters", create_request(), requried: true),
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("Scrobble", "application/json", scrobble())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def index_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["Scrobbles"],
|
||||||
|
summary: "Requests a list of current and recent Listen activities for an account",
|
||||||
|
operationId: "PleromaAPI.ScrobbleController.index",
|
||||||
|
parameters: [
|
||||||
|
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"} | pagination_params()
|
||||||
|
],
|
||||||
|
security: [%{"oAuth" => ["read"]}],
|
||||||
|
responses: %{
|
||||||
|
200 =>
|
||||||
|
Operation.response("Array of Scrobble", "application/json", %Schema{
|
||||||
|
type: :array,
|
||||||
|
items: scrobble()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_request do
|
||||||
|
%Schema{
|
||||||
|
type: :object,
|
||||||
|
required: [:title],
|
||||||
|
properties: %{
|
||||||
|
title: %Schema{type: :string, description: "The title of the media playing"},
|
||||||
|
album: %Schema{type: :string, description: "The album of the media playing"},
|
||||||
|
artist: %Schema{type: :string, description: "The artist of the media playing"},
|
||||||
|
length: %Schema{type: :integer, description: "The length of the media playing"},
|
||||||
|
visibility: %Schema{
|
||||||
|
allOf: [VisibilityScope],
|
||||||
|
default: "public",
|
||||||
|
description: "Scrobble visibility"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
example: %{
|
||||||
|
"title" => "Some Title",
|
||||||
|
"artist" => "Some Artist",
|
||||||
|
"album" => "Some Album",
|
||||||
|
"length" => 180_000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp scrobble do
|
||||||
|
%Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
id: %Schema{type: :string},
|
||||||
|
account: Account,
|
||||||
|
title: %Schema{type: :string, description: "The title of the media playing"},
|
||||||
|
album: %Schema{type: :string, description: "The album of the media playing"},
|
||||||
|
artist: %Schema{type: :string, description: "The artist of the media playing"},
|
||||||
|
length: %Schema{
|
||||||
|
type: :integer,
|
||||||
|
description: "The length of the media playing",
|
||||||
|
nullable: true
|
||||||
|
},
|
||||||
|
created_at: %Schema{type: :string, format: :"date-time"}
|
||||||
|
},
|
||||||
|
example: %{
|
||||||
|
"id" => "1234",
|
||||||
|
"account" => Account.schema().example,
|
||||||
|
"title" => "Some Title",
|
||||||
|
"artist" => "Some Artist",
|
||||||
|
"album" => "Some Album",
|
||||||
|
"length" => 180_000,
|
||||||
|
"created_at" => "2019-09-28T12:40:45.000Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -347,11 +347,14 @@ def check_expiry_date(expiry_str) do
|
||||||
|> check_expiry_date()
|
|> check_expiry_date()
|
||||||
end
|
end
|
||||||
|
|
||||||
def listen(user, %{"title" => _} = data) do
|
def listen(user, data) do
|
||||||
with visibility <- data["visibility"] || "public",
|
visibility = Map.get(data, :visibility, "public")
|
||||||
{to, cc} <- get_to_and_cc(user, [], nil, visibility, nil),
|
|
||||||
|
with {to, cc} <- get_to_and_cc(user, [], nil, visibility, nil),
|
||||||
listen_data <-
|
listen_data <-
|
||||||
Map.take(data, ["album", "artist", "title", "length"])
|
data
|
||||||
|
|> Map.take([:album, :artist, :title, :length])
|
||||||
|
|> Map.new(fn {key, value} -> {to_string(key), value} end)
|
||||||
|> Map.put("type", "Audio")
|
|> Map.put("type", "Audio")
|
||||||
|> Map.put("to", to)
|
|> Map.put("to", to)
|
||||||
|> Map.put("cc", cc)
|
|> Map.put("cc", cc)
|
||||||
|
|
|
@ -436,27 +436,6 @@ def render("attachment.json", %{attachment: attachment}) do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("listen.json", %{activity: %Activity{data: %{"type" => "Listen"}} = activity} = opts) do
|
|
||||||
object = Object.normalize(activity)
|
|
||||||
|
|
||||||
user = get_user(activity.data["actor"])
|
|
||||||
created_at = Utils.to_masto_date(activity.data["published"])
|
|
||||||
|
|
||||||
%{
|
|
||||||
id: activity.id,
|
|
||||||
account: AccountView.render("show.json", %{user: user, for: opts[:for]}),
|
|
||||||
created_at: created_at,
|
|
||||||
title: object.data["title"] |> HTML.strip_tags(),
|
|
||||||
artist: object.data["artist"] |> HTML.strip_tags(),
|
|
||||||
album: object.data["album"] |> HTML.strip_tags(),
|
|
||||||
length: object.data["length"]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def render("listens.json", opts) do
|
|
||||||
safe_render_many(opts.activities, StatusView, "listen.json", opts)
|
|
||||||
end
|
|
||||||
|
|
||||||
def render("context.json", %{activity: activity, activities: activities, user: user}) do
|
def render("context.json", %{activity: activity, activities: activities, user: user}) do
|
||||||
%{ancestors: ancestors, descendants: descendants} =
|
%{ancestors: ancestors, descendants: descendants} =
|
||||||
activities
|
activities
|
||||||
|
|
|
@ -5,34 +5,27 @@
|
||||||
defmodule Pleroma.Web.PleromaAPI.ScrobbleController do
|
defmodule Pleroma.Web.PleromaAPI.ScrobbleController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, fetch_integer_param: 2]
|
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
|
||||||
|
|
||||||
alias Pleroma.Plugs.OAuthScopesPlug
|
alias Pleroma.Plugs.OAuthScopesPlug
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
|
||||||
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
%{scopes: ["read"], fallback: :proceed_unauthenticated} when action == :user_scrobbles
|
%{scopes: ["read"], fallback: :proceed_unauthenticated} when action == :index
|
||||||
)
|
)
|
||||||
|
|
||||||
plug(OAuthScopesPlug, %{scopes: ["write"]} when action != :user_scrobbles)
|
plug(OAuthScopesPlug, %{scopes: ["write"]} when action == :create)
|
||||||
|
|
||||||
def new_scrobble(%{assigns: %{user: user}} = conn, %{"title" => _} = params) do
|
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaScrobbleOperation
|
||||||
params =
|
|
||||||
if !params["length"] do
|
|
||||||
params
|
|
||||||
else
|
|
||||||
params
|
|
||||||
|> Map.put("length", fetch_integer_param(params, "length"))
|
|
||||||
end
|
|
||||||
|
|
||||||
|
def create(%{assigns: %{user: user}, body_params: params} = conn, _) do
|
||||||
with {:ok, activity} <- CommonAPI.listen(user, params) do
|
with {:ok, activity} <- CommonAPI.listen(user, params) do
|
||||||
conn
|
render(conn, "show.json", activity: activity, for: user)
|
||||||
|> put_view(StatusView)
|
|
||||||
|> render("listen.json", %{activity: activity, for: user})
|
|
||||||
else
|
else
|
||||||
{:error, message} ->
|
{:error, message} ->
|
||||||
conn
|
conn
|
||||||
|
@ -41,16 +34,18 @@ def new_scrobble(%{assigns: %{user: user}} = conn, %{"title" => _} = params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_scrobbles(%{assigns: %{user: reading_user}} = conn, params) do
|
def index(%{assigns: %{user: reading_user}} = conn, %{id: id} = params) do
|
||||||
with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do
|
with %User{} = user <- User.get_cached_by_nickname_or_id(id, for: reading_user) do
|
||||||
params = Map.put(params, "type", ["Listen"])
|
params =
|
||||||
|
params
|
||||||
|
|> Map.new(fn {key, value} -> {to_string(key), value} end)
|
||||||
|
|> Map.put("type", ["Listen"])
|
||||||
|
|
||||||
activities = ActivityPub.fetch_user_abstract_activities(user, reading_user, params)
|
activities = ActivityPub.fetch_user_abstract_activities(user, reading_user, params)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(activities)
|
|> add_link_headers(activities)
|
||||||
|> put_view(StatusView)
|
|> render("index.json", %{
|
||||||
|> render("listens.json", %{
|
|
||||||
activities: activities,
|
activities: activities,
|
||||||
for: reading_user,
|
for: reading_user,
|
||||||
as: :activity
|
as: :activity
|
||||||
|
|
37
lib/pleroma/web/pleroma_api/views/scrobble_view.ex
Normal file
37
lib/pleroma/web/pleroma_api/views/scrobble_view.ex
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.PleromaAPI.ScrobbleView do
|
||||||
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.HTML
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
|
||||||
|
def render("show.json", %{activity: %Activity{data: %{"type" => "Listen"}} = activity} = opts) do
|
||||||
|
object = Object.normalize(activity)
|
||||||
|
|
||||||
|
user = StatusView.get_user(activity.data["actor"])
|
||||||
|
created_at = Utils.to_masto_date(activity.data["published"])
|
||||||
|
|
||||||
|
%{
|
||||||
|
id: activity.id,
|
||||||
|
account: AccountView.render("show.json", %{user: user, for: opts[:for]}),
|
||||||
|
created_at: created_at,
|
||||||
|
title: object.data["title"] |> HTML.strip_tags(),
|
||||||
|
artist: object.data["artist"] |> HTML.strip_tags(),
|
||||||
|
album: object.data["album"] |> HTML.strip_tags(),
|
||||||
|
length: object.data["length"]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def render("index.json", opts) do
|
||||||
|
safe_render_many(opts.activities, __MODULE__, "show.json", opts)
|
||||||
|
end
|
||||||
|
end
|
|
@ -325,7 +325,7 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/mascot", MascotController, :show)
|
get("/mascot", MascotController, :show)
|
||||||
put("/mascot", MascotController, :update)
|
put("/mascot", MascotController, :update)
|
||||||
|
|
||||||
post("/scrobble", ScrobbleController, :new_scrobble)
|
post("/scrobble", ScrobbleController, :create)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope [] do
|
scope [] do
|
||||||
|
@ -345,7 +345,7 @@ defmodule Pleroma.Web.Router do
|
||||||
|
|
||||||
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
|
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
|
||||||
pipe_through(:api)
|
pipe_through(:api)
|
||||||
get("/accounts/:id/scrobbles", ScrobbleController, :user_scrobbles)
|
get("/accounts/:id/scrobbles", ScrobbleController, :index)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
||||||
|
|
|
@ -841,10 +841,10 @@ test "returns a valid activity" do
|
||||||
|
|
||||||
{:ok, activity} =
|
{:ok, activity} =
|
||||||
CommonAPI.listen(user, %{
|
CommonAPI.listen(user, %{
|
||||||
"title" => "lain radio episode 1",
|
title: "lain radio episode 1",
|
||||||
"album" => "lain radio",
|
album: "lain radio",
|
||||||
"artist" => "lain",
|
artist: "lain",
|
||||||
"length" => 180_000
|
length: 180_000
|
||||||
})
|
})
|
||||||
|
|
||||||
object = Object.normalize(activity)
|
object = Object.normalize(activity)
|
||||||
|
@ -859,11 +859,11 @@ test "respects visibility=private" do
|
||||||
|
|
||||||
{:ok, activity} =
|
{:ok, activity} =
|
||||||
CommonAPI.listen(user, %{
|
CommonAPI.listen(user, %{
|
||||||
"title" => "lain radio episode 1",
|
title: "lain radio episode 1",
|
||||||
"album" => "lain radio",
|
album: "lain radio",
|
||||||
"artist" => "lain",
|
artist: "lain",
|
||||||
"length" => 180_000,
|
length: 180_000,
|
||||||
"visibility" => "private"
|
visibility: "private"
|
||||||
})
|
})
|
||||||
|
|
||||||
object = Object.normalize(activity)
|
object = Object.normalize(activity)
|
||||||
|
|
|
@ -620,14 +620,4 @@ test "visibility/list" do
|
||||||
|
|
||||||
assert status.visibility == "list"
|
assert status.visibility == "list"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "successfully renders a Listen activity (pleroma extension)" do
|
|
||||||
listen_activity = insert(:listen)
|
|
||||||
|
|
||||||
status = StatusView.render("listen.json", activity: listen_activity)
|
|
||||||
|
|
||||||
assert status.length == listen_activity.data["object"]["length"]
|
|
||||||
assert status.title == listen_activity.data["object"]["title"]
|
|
||||||
assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,14 +12,16 @@ test "works correctly" do
|
||||||
%{conn: conn} = oauth_access(["write"])
|
%{conn: conn} = oauth_access(["write"])
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
post(conn, "/api/v1/pleroma/scrobble", %{
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/pleroma/scrobble", %{
|
||||||
"title" => "lain radio episode 1",
|
"title" => "lain radio episode 1",
|
||||||
"artist" => "lain",
|
"artist" => "lain",
|
||||||
"album" => "lain radio",
|
"album" => "lain radio",
|
||||||
"length" => "180000"
|
"length" => "180000"
|
||||||
})
|
})
|
||||||
|
|
||||||
assert %{"title" => "lain radio episode 1"} = json_response(conn, 200)
|
assert %{"title" => "lain radio episode 1"} = json_response_and_validate_schema(conn, 200)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -29,28 +31,28 @@ test "works correctly" do
|
||||||
|
|
||||||
{:ok, _activity} =
|
{:ok, _activity} =
|
||||||
CommonAPI.listen(user, %{
|
CommonAPI.listen(user, %{
|
||||||
"title" => "lain radio episode 1",
|
title: "lain radio episode 1",
|
||||||
"artist" => "lain",
|
artist: "lain",
|
||||||
"album" => "lain radio"
|
album: "lain radio"
|
||||||
})
|
})
|
||||||
|
|
||||||
{:ok, _activity} =
|
{:ok, _activity} =
|
||||||
CommonAPI.listen(user, %{
|
CommonAPI.listen(user, %{
|
||||||
"title" => "lain radio episode 2",
|
title: "lain radio episode 2",
|
||||||
"artist" => "lain",
|
artist: "lain",
|
||||||
"album" => "lain radio"
|
album: "lain radio"
|
||||||
})
|
})
|
||||||
|
|
||||||
{:ok, _activity} =
|
{:ok, _activity} =
|
||||||
CommonAPI.listen(user, %{
|
CommonAPI.listen(user, %{
|
||||||
"title" => "lain radio episode 3",
|
title: "lain radio episode 3",
|
||||||
"artist" => "lain",
|
artist: "lain",
|
||||||
"album" => "lain radio"
|
album: "lain radio"
|
||||||
})
|
})
|
||||||
|
|
||||||
conn = get(conn, "/api/v1/pleroma/accounts/#{user.id}/scrobbles")
|
conn = get(conn, "/api/v1/pleroma/accounts/#{user.id}/scrobbles")
|
||||||
|
|
||||||
result = json_response(conn, 200)
|
result = json_response_and_validate_schema(conn, 200)
|
||||||
|
|
||||||
assert length(result) == 3
|
assert length(result) == 3
|
||||||
end
|
end
|
||||||
|
|
20
test/web/pleroma_api/views/scrobble_view_test.exs
Normal file
20
test/web/pleroma_api/views/scrobble_view_test.exs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.PleromaAPI.StatusViewTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
alias Pleroma.Web.PleromaAPI.ScrobbleView
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "successfully renders a Listen activity (pleroma extension)" do
|
||||||
|
listen_activity = insert(:listen)
|
||||||
|
|
||||||
|
status = ScrobbleView.render("show.json", activity: listen_activity)
|
||||||
|
|
||||||
|
assert status.length == listen_activity.data["object"]["length"]
|
||||||
|
assert status.title == listen_activity.data["object"]["title"]
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue