@@ -89,11 +89,10 @@ defmodule Pleroma.Filter do | |||
|> Repo.delete() | |||
end | |||
def update(%Pleroma.Filter{} = filter) do | |||
destination = Map.from_struct(filter) | |||
Pleroma.Filter.get(filter.filter_id, %{id: filter.user_id}) | |||
|> cast(destination, [:phrase, :context, :hide, :expires_at, :whole_word]) | |||
def update(%Pleroma.Filter{} = filter, params) do | |||
filter | |||
|> cast(params, [:phrase, :context, :hide, :expires_at, :whole_word]) | |||
|> validate_required([:phrase, :context]) | |||
|> Repo.update() | |||
end | |||
end |
@@ -0,0 +1,89 @@ | |||
# 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.FilterOperation do | |||
alias OpenApiSpex.Operation | |||
alias OpenApiSpex.Schema | |||
alias Pleroma.Web.ApiSpec.Helpers | |||
alias Pleroma.Web.ApiSpec.Schemas.Filter | |||
alias Pleroma.Web.ApiSpec.Schemas.FilterCreateRequest | |||
alias Pleroma.Web.ApiSpec.Schemas.FiltersResponse | |||
alias Pleroma.Web.ApiSpec.Schemas.FilterUpdateRequest | |||
def open_api_operation(action) do | |||
operation = String.to_existing_atom("#{action}_operation") | |||
apply(__MODULE__, operation, []) | |||
end | |||
def index_operation do | |||
%Operation{ | |||
tags: ["apps"], | |||
summary: "View all filters", | |||
operationId: "FilterController.index", | |||
security: [%{"oAuth" => ["read:filters"]}], | |||
responses: %{ | |||
200 => Operation.response("Filters", "application/json", FiltersResponse) | |||
} | |||
} | |||
end | |||
def create_operation do | |||
%Operation{ | |||
tags: ["apps"], | |||
summary: "Create a filter", | |||
operationId: "FilterController.create", | |||
requestBody: Helpers.request_body("Parameters", FilterCreateRequest, required: true), | |||
security: [%{"oAuth" => ["write:filters"]}], | |||
responses: %{200 => Operation.response("Filter", "application/json", Filter)} | |||
} | |||
end | |||
def show_operation do | |||
%Operation{ | |||
tags: ["apps"], | |||
summary: "View all filters", | |||
parameters: [id_param()], | |||
operationId: "FilterController.show", | |||
security: [%{"oAuth" => ["read:filters"]}], | |||
responses: %{ | |||
200 => Operation.response("Filter", "application/json", Filter) | |||
} | |||
} | |||
end | |||
def update_operation do | |||
%Operation{ | |||
tags: ["apps"], | |||
summary: "Update a filter", | |||
parameters: [id_param()], | |||
operationId: "FilterController.update", | |||
requestBody: Helpers.request_body("Parameters", FilterUpdateRequest, required: true), | |||
security: [%{"oAuth" => ["write:filters"]}], | |||
responses: %{ | |||
200 => Operation.response("Filter", "application/json", Filter) | |||
} | |||
} | |||
end | |||
def delete_operation do | |||
%Operation{ | |||
tags: ["apps"], | |||
summary: "Remove a filter", | |||
parameters: [id_param()], | |||
operationId: "FilterController.delete", | |||
security: [%{"oAuth" => ["write:filters"]}], | |||
responses: %{ | |||
200 => | |||
Operation.response("Filter", "application/json", %Schema{ | |||
type: :object, | |||
description: "Empty object" | |||
}) | |||
} | |||
} | |||
end | |||
defp id_param do | |||
Operation.parameter(:id, :path, :string, "Filter ID", example: "123", required: true) | |||
end | |||
end |
@@ -0,0 +1,51 @@ | |||
# 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.Filter do | |||
alias OpenApiSpex.Schema | |||
require OpenApiSpex | |||
OpenApiSpex.schema(%{ | |||
title: "Filter", | |||
type: :object, | |||
properties: %{ | |||
id: %Schema{type: :string}, | |||
phrase: %Schema{type: :string, description: "The text to be filtered"}, | |||
context: %Schema{ | |||
type: :array, | |||
items: %Schema{type: :string, enum: ["home", "notifications", "public", "thread"]}, | |||
description: "The contexts in which the filter should be applied." | |||
}, | |||
expires_at: %Schema{ | |||
type: :string, | |||
format: :"date-time", | |||
description: | |||
"When the filter should no longer be applied. String (ISO 8601 Datetime), or null if the filter does not expire.", | |||
nullable: true | |||
}, | |||
irreversible: %Schema{ | |||
type: :boolean, | |||
description: | |||
"Should matching entities in home and notifications be dropped by the server?" | |||
}, | |||
whole_word: %Schema{ | |||
type: :boolean, | |||
description: "Should the filter consider word boundaries?" | |||
} | |||
}, | |||
example: %{ | |||
"id" => "5580", | |||
"phrase" => "@twitter.com", | |||
"context" => [ | |||
"home", | |||
"notifications", | |||
"public", | |||
"thread" | |||
], | |||
"whole_word" => false, | |||
"expires_at" => nil, | |||
"irreversible" => true | |||
} | |||
}) | |||
end |
@@ -0,0 +1,30 @@ | |||
# 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.FilterCreateRequest do | |||
alias OpenApiSpex.Schema | |||
require OpenApiSpex | |||
OpenApiSpex.schema(%{ | |||
title: "FilterCreateRequest", | |||
allOf: [ | |||
%OpenApiSpex.Reference{"$ref": "#/components/schemas/FilterUpdateRequest"}, | |||
%Schema{ | |||
type: :object, | |||
properties: %{ | |||
irreversible: %Schema{ | |||
type: :bolean, | |||
description: | |||
"Should the server irreversibly drop matching entities from home and notifications?", | |||
default: false | |||
} | |||
} | |||
} | |||
], | |||
example: %{ | |||
"phrase" => "knights", | |||
"context" => ["home"] | |||
} | |||
}) | |||
end |
@@ -0,0 +1,41 @@ | |||
# 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.FilterUpdateRequest do | |||
alias OpenApiSpex.Schema | |||
require OpenApiSpex | |||
OpenApiSpex.schema(%{ | |||
title: "FilterUpdateRequest", | |||
type: :object, | |||
properties: %{ | |||
phrase: %Schema{type: :string, description: "The text to be filtered"}, | |||
context: %Schema{ | |||
type: :array, | |||
items: %Schema{type: :string, enum: ["home", "notifications", "public", "thread"]}, | |||
description: | |||
"Array of enumerable strings `home`, `notifications`, `public`, `thread`. At least one context must be specified." | |||
}, | |||
irreversible: %Schema{ | |||
type: :bolean, | |||
description: | |||
"Should the server irreversibly drop matching entities from home and notifications?" | |||
}, | |||
whole_word: %Schema{type: :bolean, description: "Consider word boundaries?", default: true} | |||
# TODO: probably should implement filter expiration | |||
# expires_in: %Schema{ | |||
# type: :string, | |||
# format: :"date-time", | |||
# description: | |||
# "ISO 8601 Datetime for when the filter expires. Otherwise, | |||
# null for a filter that doesn't expire." | |||
# } | |||
}, | |||
required: [:phrase, :context], | |||
example: %{ | |||
"phrase" => "knights", | |||
"context" => ["home"] | |||
} | |||
}) | |||
end |
@@ -0,0 +1,40 @@ | |||
# 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.FiltersResponse do | |||
require OpenApiSpex | |||
alias Pleroma.Web.ApiSpec.Schemas.Filter | |||
OpenApiSpex.schema(%{ | |||
title: "FiltersResponse", | |||
description: "Array of Filters", | |||
type: :array, | |||
items: Filter, | |||
example: [ | |||
%{ | |||
"id" => "5580", | |||
"phrase" => "@twitter.com", | |||
"context" => [ | |||
"home", | |||
"notifications", | |||
"public", | |||
"thread" | |||
], | |||
"whole_word" => false, | |||
"expires_at" => nil, | |||
"irreversible" => true | |||
}, | |||
%{ | |||
"id" => "6191", | |||
"phrase" => ":eurovision2019:", | |||
"context" => [ | |||
"home" | |||
], | |||
"whole_word" => true, | |||
"expires_at" => "2019-05-21T13:47:31.333Z", | |||
"irreversible" => false | |||
} | |||
] | |||
}) | |||
end |
@@ -10,67 +10,69 @@ defmodule Pleroma.Web.MastodonAPI.FilterController do | |||
@oauth_read_actions [:show, :index] | |||
plug(Pleroma.Web.ApiSpec.CastAndValidate) | |||
plug(OAuthScopesPlug, %{scopes: ["read:filters"]} when action in @oauth_read_actions) | |||
plug( | |||
OAuthScopesPlug, | |||
%{scopes: ["write:filters"]} when action not in @oauth_read_actions | |||
) | |||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.FilterOperation | |||
@doc "GET /api/v1/filters" | |||
def index(%{assigns: %{user: user}} = conn, _) do | |||
filters = Filter.get_filters(user) | |||
render(conn, "filters.json", filters: filters) | |||
render(conn, "index.json", filters: filters) | |||
end | |||
@doc "POST /api/v1/filters" | |||
def create( | |||
%{assigns: %{user: user}} = conn, | |||
%{"phrase" => phrase, "context" => context} = params | |||
) do | |||
def create(%{assigns: %{user: user}, body_params: params} = conn, _) do | |||
query = %Filter{ | |||
user_id: user.id, | |||
phrase: phrase, | |||
context: context, | |||
hide: Map.get(params, "irreversible", false), | |||
whole_word: Map.get(params, "boolean", true) | |||
phrase: params.phrase, | |||
context: params.context, | |||
hide: params.irreversible, | |||
whole_word: params.whole_word | |||
# expires_at | |||
} | |||
{:ok, response} = Filter.create(query) | |||
render(conn, "filter.json", filter: response) | |||
render(conn, "show.json", filter: response) | |||
end | |||
@doc "GET /api/v1/filters/:id" | |||
def show(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do | |||
def show(%{assigns: %{user: user}} = conn, %{id: filter_id}) do | |||
filter = Filter.get(filter_id, user) | |||
render(conn, "filter.json", filter: filter) | |||
render(conn, "show.json", filter: filter) | |||
end | |||
@doc "PUT /api/v1/filters/:id" | |||
def update( | |||
%{assigns: %{user: user}} = conn, | |||
%{"phrase" => phrase, "context" => context, "id" => filter_id} = params | |||
%{assigns: %{user: user}, body_params: params} = conn, | |||
%{id: filter_id} | |||
) do | |||
query = %Filter{ | |||
user_id: user.id, | |||
filter_id: filter_id, | |||
phrase: phrase, | |||
context: context, | |||
hide: Map.get(params, "irreversible", nil), | |||
whole_word: Map.get(params, "boolean", true) | |||
# expires_at | |||
} | |||
{:ok, response} = Filter.update(query) | |||
render(conn, "filter.json", filter: response) | |||
params = | |||
params | |||
|> Map.from_struct() | |||
|> Map.delete(:irreversible) | |||
|> Map.put(:hide, params.irreversible) | |||
|> Enum.reject(fn {_key, value} -> is_nil(value) end) | |||
|> Map.new() | |||
# TODO: add expires_in -> expires_at | |||
with %Filter{} = filter <- Filter.get(filter_id, user), | |||
{:ok, %Filter{} = filter} <- Filter.update(filter, params) do | |||
render(conn, "show.json", filter: filter) | |||
end | |||
end | |||
@doc "DELETE /api/v1/filters/:id" | |||
def delete(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do | |||
def delete(%{assigns: %{user: user}} = conn, %{id: filter_id}) do | |||
query = %Filter{ | |||
user_id: user.id, | |||
filter_id: filter_id | |||
@@ -7,11 +7,11 @@ defmodule Pleroma.Web.MastodonAPI.FilterView do | |||
alias Pleroma.Web.CommonAPI.Utils | |||
alias Pleroma.Web.MastodonAPI.FilterView | |||
def render("filters.json", %{filters: filters} = opts) do | |||
render_many(filters, FilterView, "filter.json", opts) | |||
def render("index.json", %{filters: filters} = opts) do | |||
render_many(filters, FilterView, "show.json", opts) | |||
end | |||
def render("filter.json", %{filter: filter}) do | |||
def render("show.json", %{filter: filter}) do | |||
expires_at = | |||
if filter.expires_at do | |||
Utils.to_masto_date(filter.expires_at) | |||
@@ -141,17 +141,15 @@ defmodule Pleroma.FilterTest do | |||
context: ["home"] | |||
} | |||
query_two = %Pleroma.Filter{ | |||
user_id: user.id, | |||
filter_id: 1, | |||
changes = %{ | |||
phrase: "who", | |||
context: ["home", "timeline"] | |||
} | |||
{:ok, filter_one} = Pleroma.Filter.create(query_one) | |||
{:ok, filter_two} = Pleroma.Filter.update(query_two) | |||
{:ok, filter_two} = Pleroma.Filter.update(filter_one, changes) | |||
assert filter_one != filter_two | |||
assert filter_two.phrase == query_two.phrase | |||
assert filter_two.context == query_two.context | |||
assert filter_two.phrase == changes.phrase | |||
assert filter_two.context == changes.context | |||
end | |||
end |
@@ -5,8 +5,15 @@ | |||
defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do | |||
use Pleroma.Web.ConnCase | |||
alias Pleroma.Web.ApiSpec | |||
alias Pleroma.Web.ApiSpec.Schemas.Filter | |||
alias Pleroma.Web.ApiSpec.Schemas.FilterCreateRequest | |||
alias Pleroma.Web.ApiSpec.Schemas.FiltersResponse | |||
alias Pleroma.Web.ApiSpec.Schemas.FilterUpdateRequest | |||
alias Pleroma.Web.MastodonAPI.FilterView | |||
import OpenApiSpex.TestAssertions | |||
test "creating a filter" do | |||
%{conn: conn} = oauth_access(["write:filters"]) | |||
@@ -15,7 +22,10 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do | |||
context: ["home"] | |||
} | |||
conn = post(conn, "/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context}) | |||
conn = | |||
conn | |||
|> put_req_header("content-type", "application/json") | |||
|> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context}) | |||
assert response = json_response(conn, 200) | |||
assert response["phrase"] == filter.phrase | |||
@@ -23,6 +33,7 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do | |||
assert response["irreversible"] == false | |||
assert response["id"] != nil | |||
assert response["id"] != "" | |||
assert_schema(response, "Filter", ApiSpec.spec()) | |||
end | |||
test "fetching a list of filters" do | |||
@@ -53,9 +64,11 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do | |||
assert response == | |||
render_json( | |||
FilterView, | |||
"filters.json", | |||
"index.json", | |||
filters: [filter_two, filter_one] | |||
) | |||
assert_schema(response, "FiltersResponse", ApiSpec.spec()) | |||
end | |||
test "get a filter" do | |||
@@ -72,7 +85,8 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do | |||
conn = get(conn, "/api/v1/filters/#{filter.filter_id}") | |||
assert _response = json_response(conn, 200) | |||
assert response = json_response(conn, 200) | |||
assert_schema(response, "Filter", ApiSpec.spec()) | |||
end | |||
test "update a filter" do | |||
@@ -82,7 +96,8 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do | |||
user_id: user.id, | |||
filter_id: 2, | |||
phrase: "knight", | |||
context: ["home"] | |||
context: ["home"], | |||
hide: true | |||
} | |||
{:ok, _filter} = Pleroma.Filter.create(query) | |||
@@ -93,7 +108,9 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do | |||
} | |||
conn = | |||
put(conn, "/api/v1/filters/#{query.filter_id}", %{ | |||
conn | |||
|> put_req_header("content-type", "application/json") | |||
|> put("/api/v1/filters/#{query.filter_id}", %{ | |||
phrase: new.phrase, | |||
context: new.context | |||
}) | |||
@@ -101,6 +118,8 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do | |||
assert response = json_response(conn, 200) | |||
assert response["phrase"] == new.phrase | |||
assert response["context"] == new.context | |||
assert response["irreversible"] == true | |||
assert_schema(response, "Filter", ApiSpec.spec()) | |||
end | |||
test "delete a filter" do | |||
@@ -120,4 +139,30 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do | |||
assert response = json_response(conn, 200) | |||
assert response == %{} | |||
end | |||
describe "OpenAPI" do | |||
test "Filter example matches schema" do | |||
api_spec = ApiSpec.spec() | |||
schema = Filter.schema() | |||
assert_schema(schema.example, "Filter", api_spec) | |||
end | |||
test "FiltersResponse example matches schema" do | |||
api_spec = ApiSpec.spec() | |||
schema = FiltersResponse.schema() | |||
assert_schema(schema.example, "FiltersResponse", api_spec) | |||
end | |||
test "FilterCreateRequest example matches schema" do | |||
api_spec = ApiSpec.spec() | |||
schema = FilterCreateRequest.schema() | |||
assert_schema(schema.example, "FilterCreateRequest", api_spec) | |||
end | |||
test "FilterUpdateRequest example matches schema" do | |||
api_spec = ApiSpec.spec() | |||
schema = FilterUpdateRequest.schema() | |||
assert_schema(schema.example, "FilterUpdateRequest", api_spec) | |||
end | |||
end | |||
end |