Add OpenAPI spec for SearchController See merge request pleroma/pleroma!24842168-media-preview-proxy
@@ -556,11 +556,12 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do | |||
} | |||
end | |||
defp array_of_accounts do | |||
def array_of_accounts do | |||
%Schema{ | |||
title: "ArrayOfAccounts", | |||
type: :array, | |||
items: Account | |||
items: Account, | |||
example: [Account.schema().example] | |||
} | |||
end | |||
@@ -0,0 +1,207 @@ | |||
# 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.SearchOperation do | |||
alias OpenApiSpex.Operation | |||
alias OpenApiSpex.Schema | |||
alias Pleroma.Web.ApiSpec.AccountOperation | |||
alias Pleroma.Web.ApiSpec.Schemas.Account | |||
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike | |||
alias Pleroma.Web.ApiSpec.Schemas.FlakeID | |||
alias Pleroma.Web.ApiSpec.Schemas.Status | |||
alias Pleroma.Web.ApiSpec.Schemas.Tag | |||
import Pleroma.Web.ApiSpec.Helpers | |||
def open_api_operation(action) do | |||
operation = String.to_existing_atom("#{action}_operation") | |||
apply(__MODULE__, operation, []) | |||
end | |||
def account_search_operation do | |||
%Operation{ | |||
tags: ["Search"], | |||
summary: "Search for matching accounts by username or display name", | |||
operationId: "SearchController.account_search", | |||
parameters: [ | |||
Operation.parameter(:q, :query, %Schema{type: :string}, "What to search for", | |||
required: true | |||
), | |||
Operation.parameter( | |||
:limit, | |||
:query, | |||
%Schema{type: :integer, default: 40}, | |||
"Maximum number of results" | |||
), | |||
Operation.parameter( | |||
:resolve, | |||
:query, | |||
%Schema{allOf: [BooleanLike], default: false}, | |||
"Attempt WebFinger lookup. Use this when `q` is an exact address." | |||
), | |||
Operation.parameter( | |||
:following, | |||
:query, | |||
%Schema{allOf: [BooleanLike], default: false}, | |||
"Only include accounts that the user is following" | |||
) | |||
], | |||
responses: %{ | |||
200 => | |||
Operation.response( | |||
"Array of Account", | |||
"application/json", | |||
AccountOperation.array_of_accounts() | |||
) | |||
} | |||
} | |||
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" | |||
) | |||
| pagination_params() | |||
], | |||
responses: %{ | |||
200 => Operation.response("Results", "application/json", results()) | |||
} | |||
} | |||
end | |||
def search2_operation do | |||
%Operation{ | |||
tags: ["Search"], | |||
summary: "Search results", | |||
security: [%{"oAuth" => ["read:search"]}], | |||
operationId: "SearchController.search2", | |||
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}, "What to search for", | |||
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" | |||
) | |||
| pagination_params() | |||
], | |||
responses: %{ | |||
200 => Operation.response("Results", "application/json", results2()) | |||
} | |||
} | |||
end | |||
defp results2 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: Tag, | |||
description: "Hashtags which match the given query" | |||
} | |||
}, | |||
example: %{ | |||
"accounts" => [Account.schema().example], | |||
"statuses" => [Status.schema().example], | |||
"hashtags" => [Tag.schema().example] | |||
} | |||
} | |||
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 |
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do | |||
alias Pleroma.Web.ApiSpec.Schemas.Emoji | |||
alias Pleroma.Web.ApiSpec.Schemas.FlakeID | |||
alias Pleroma.Web.ApiSpec.Schemas.Poll | |||
alias Pleroma.Web.ApiSpec.Schemas.Tag | |||
alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope | |||
require OpenApiSpex | |||
@@ -106,16 +107,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do | |||
replies_count: %Schema{type: :integer}, | |||
sensitive: %Schema{type: :boolean}, | |||
spoiler_text: %Schema{type: :string}, | |||
tags: %Schema{ | |||
type: :array, | |||
items: %Schema{ | |||
type: :object, | |||
properties: %{ | |||
name: %Schema{type: :string}, | |||
url: %Schema{type: :string, format: :uri} | |||
} | |||
} | |||
}, | |||
tags: %Schema{type: :array, items: Tag}, | |||
uri: %Schema{type: :string, format: :uri}, | |||
url: %Schema{type: :string, nullable: true, format: :uri}, | |||
visibility: VisibilityScope | |||
@@ -0,0 +1,27 @@ | |||
# 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.Schemas.Tag do | |||
alias OpenApiSpex.Schema | |||
require OpenApiSpex | |||
OpenApiSpex.schema(%{ | |||
title: "Tag", | |||
description: "Represents a hashtag used within the content of a status", | |||
type: :object, | |||
properties: %{ | |||
name: %Schema{type: :string, description: "The value of the hashtag after the # sign"}, | |||
url: %Schema{ | |||
type: :string, | |||
format: :uri, | |||
description: "A link to the hashtag on the instance" | |||
} | |||
}, | |||
example: %{ | |||
name: "cofe", | |||
url: "https://lain.com/tag/cofe" | |||
} | |||
}) | |||
end |
@@ -5,7 +5,7 @@ | |||
defmodule Pleroma.Web.MastodonAPI.SearchController do | |||
use Pleroma.Web, :controller | |||
import Pleroma.Web.ControllerHelper, only: [fetch_integer_param: 2, skip_relationships?: 1] | |||
import Pleroma.Web.ControllerHelper, only: [skip_relationships?: 1] | |||
alias Pleroma.Activity | |||
alias Pleroma.Plugs.OAuthScopesPlug | |||
@@ -18,6 +18,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do | |||
require Logger | |||
plug(Pleroma.Web.ApiSpec.CastAndValidate) | |||
# Note: Mastodon doesn't allow unauthenticated access (requires read:accounts / read:search) | |||
plug(OAuthScopesPlug, %{scopes: ["read:search"], fallback: :proceed_unauthenticated}) | |||
@@ -25,7 +27,9 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do | |||
plug(RateLimiter, [name: :search] when action in [:search, :search2, :account_search]) | |||
def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do | |||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.SearchOperation | |||
def account_search(%{assigns: %{user: user}} = conn, %{q: query} = params) do | |||
accounts = User.search(query, search_options(params, user)) | |||
conn | |||
@@ -36,7 +40,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do | |||
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 | |||
options = search_options(params, user) | |||
timeout = Keyword.get(Repo.config(), :timeout, 15_000) | |||
default_values = %{"statuses" => [], "accounts" => [], "hashtags" => []} | |||
@@ -44,7 +48,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do | |||
result = | |||
default_values | |||
|> Enum.map(fn {resource, default_value} -> | |||
if params["type"] in [nil, resource] do | |||
if params[:type] in [nil, resource] do | |||
{resource, fn -> resource_search(version, resource, query, options) end} | |||
else | |||
{resource, fn -> default_value end} | |||
@@ -68,11 +72,11 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do | |||
defp search_options(params, user) do | |||
[ | |||
skip_relationships: skip_relationships?(params), | |||
resolve: params["resolve"] == "true", | |||
following: params["following"] == "true", | |||
limit: fetch_integer_param(params, "limit"), | |||
offset: fetch_integer_param(params, "offset"), | |||
type: params["type"], | |||
resolve: params[:resolve], | |||
following: params[:following], | |||
limit: params[:limit], | |||
offset: params[:offset], | |||
type: params[:type], | |||
author: get_author(params), | |||
for_user: user | |||
] | |||
@@ -135,7 +139,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do | |||
end | |||
end | |||
defp get_author(%{"account_id" => account_id}) when is_binary(account_id), | |||
defp get_author(%{account_id: account_id}) when is_binary(account_id), | |||
do: User.get_cached_by_id(account_id) | |||
defp get_author(_params), do: nil | |||
@@ -27,8 +27,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do | |||
capture_log(fn -> | |||
results = | |||
conn | |||
|> get("/api/v2/search", %{"q" => "2hu"}) | |||
|> json_response(200) | |||
|> get("/api/v2/search?q=2hu") | |||
|> json_response_and_validate_schema(200) | |||
assert results["accounts"] == [] | |||
assert results["statuses"] == [] | |||
@@ -54,8 +54,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do | |||
results = | |||
conn | |||
|> get("/api/v2/search", %{"q" => "2hu #private"}) | |||
|> json_response(200) | |||
|> get("/api/v2/search?#{URI.encode_query(%{q: "2hu #private"})}") | |||
|> json_response_and_validate_schema(200) | |||
[account | _] = results["accounts"] | |||
assert account["id"] == to_string(user_three.id) | |||
@@ -68,8 +68,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do | |||
assert status["id"] == to_string(activity.id) | |||
results = | |||
get(conn, "/api/v2/search", %{"q" => "天子"}) | |||
|> json_response(200) | |||
get(conn, "/api/v2/search?q=天子") | |||
|> json_response_and_validate_schema(200) | |||
[status] = results["statuses"] | |||
assert status["id"] == to_string(activity.id) | |||
@@ -89,8 +89,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do | |||
conn | |||
|> assign(:user, user) | |||
|> assign(:token, insert(:oauth_token, user: user, scopes: ["read"])) | |||
|> get("/api/v2/search", %{"q" => "Agent"}) | |||
|> json_response(200) | |||
|> get("/api/v2/search?q=Agent") | |||
|> json_response_and_validate_schema(200) | |||
status_ids = Enum.map(results["statuses"], fn g -> g["id"] end) | |||
@@ -107,8 +107,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do | |||
results = | |||
conn | |||
|> get("/api/v1/accounts/search", %{"q" => "shp"}) | |||
|> json_response(200) | |||
|> get("/api/v1/accounts/search?q=shp") | |||
|> json_response_and_validate_schema(200) | |||
result_ids = for result <- results, do: result["acct"] | |||
@@ -117,8 +117,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do | |||
results = | |||
conn | |||
|> get("/api/v1/accounts/search", %{"q" => "2hu"}) | |||
|> json_response(200) | |||
|> get("/api/v1/accounts/search?q=2hu") | |||
|> json_response_and_validate_schema(200) | |||
result_ids = for result <- results, do: result["acct"] | |||
@@ -130,8 +130,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do | |||
results = | |||
conn | |||
|> get("/api/v1/accounts/search", %{"q" => "shp@shitposter.club xxx "}) | |||
|> json_response(200) | |||
|> get("/api/v1/accounts/search?q=shp@shitposter.club xxx") | |||
|> json_response_and_validate_schema(200) | |||
assert length(results) == 1 | |||
end | |||
@@ -146,8 +146,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do | |||
capture_log(fn -> | |||
results = | |||
conn | |||
|> get("/api/v1/search", %{"q" => "2hu"}) | |||
|> json_response(200) | |||
|> get("/api/v1/search?q=2hu") | |||
|> json_response_and_validate_schema(200) | |||
assert results["accounts"] == [] | |||
assert results["statuses"] == [] | |||
@@ -173,8 +173,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do | |||
results = | |||
conn | |||
|> get("/api/v1/search", %{"q" => "2hu"}) | |||
|> json_response(200) | |||
|> get("/api/v1/search?q=2hu") | |||
|> json_response_and_validate_schema(200) | |||
[account | _] = results["accounts"] | |||
assert account["id"] == to_string(user_three.id) | |||
@@ -194,8 +194,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do | |||
results = | |||
conn | |||
|> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"}) | |||
|> json_response(200) | |||
|> get("/api/v1/search?q=https://shitposter.club/notice/2827873") | |||
|> json_response_and_validate_schema(200) | |||
[status, %{"id" => ^activity_id}] = results["statuses"] | |||
@@ -212,10 +212,12 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do | |||
}) | |||
capture_log(fn -> | |||
q = Object.normalize(activity).data["id"] | |||
results = | |||
conn | |||
|> get("/api/v1/search", %{"q" => Object.normalize(activity).data["id"]}) | |||
|> json_response(200) | |||
|> get("/api/v1/search?q=#{q}") | |||
|> json_response_and_validate_schema(200) | |||
[] = results["statuses"] | |||
end) | |||
@@ -228,8 +230,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do | |||
conn | |||
|> assign(:user, user) | |||
|> assign(:token, insert(:oauth_token, user: user, scopes: ["read"])) | |||
|> get("/api/v1/search", %{"q" => "mike@osada.macgirvin.com", "resolve" => "true"}) | |||
|> json_response(200) | |||
|> get("/api/v1/search?q=mike@osada.macgirvin.com&resolve=true") | |||
|> json_response_and_validate_schema(200) | |||
[account] = results["accounts"] | |||
assert account["acct"] == "mike@osada.macgirvin.com" | |||
@@ -238,8 +240,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do | |||
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(200) | |||
|> get("/api/v1/search?q=mike@osada.macgirvin.com&resolve=false") | |||
|> json_response_and_validate_schema(200) | |||
assert [] == results["accounts"] | |||
end | |||
@@ -254,16 +256,16 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do | |||
result = | |||
conn | |||
|> get("/api/v1/search", %{"q" => "2hu", "limit" => 1}) | |||
|> get("/api/v1/search?q=2hu&limit=1") | |||
assert results = json_response(result, 200) | |||
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(200) | |||
|> 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"] | |||
@@ -279,13 +281,13 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do | |||
assert %{"statuses" => [_activity], "accounts" => [], "hashtags" => []} = | |||
conn | |||
|> get("/api/v1/search", %{"q" => "2hu", "type" => "statuses"}) | |||
|> json_response(200) | |||
|> 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(200) | |||
|> 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 | |||
@@ -297,8 +299,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do | |||
results = | |||
conn | |||
|> get("/api/v1/search", %{"q" => "2hu", "account_id" => user.id}) | |||
|> json_response(200) | |||
|> 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 | |||
@@ -306,8 +308,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do | |||
results = | |||
conn | |||
|> get("/api/v1/search", %{"q" => "2hu", "account_id" => user_two.id}) | |||
|> json_response(200) | |||
|> 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 | |||