Merge remote-tracking branch 'mkljczk-github/fork' into fork

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2024-05-14 18:11:23 +02:00
commit adbd40049d
22 changed files with 348 additions and 46 deletions

View file

@ -1,4 +1,4 @@
A fork of Pleroma/Rebased. More information soon.
`pl`. A fork of Pleroma/Rebased. More information soon.
---

View file

@ -1,10 +1,10 @@
{
"name": "Rebased",
"description": "Rebased, the recommended backend for Soapbox written in Elixir.",
"name": "pl",
"description": "Federated social media software, a fork of Pleroma/Rebased",
"keywords": [
"fediverse"
],
"website": "https://soapbox.pub",
"website": "https://github.com/mkljczk/pl",
"dokku": {
"plugins": [
"postgres"

View file

@ -1907,3 +1907,52 @@ Note that this differs from the Mastodon API variant: Mastodon API only returns
```json
{}
```
## `GET /api/v1/pleroma/admin/rules`
### List rules
- Response: JSON, list of rules
```json
[
{
"id": "1",
"priority": 1,
"text": "There are no rules",
"hint": null
}
]
```
## `POST /api/v1/pleroma/admin/rules`
### Create a rule
- Params:
- `text`: string, required, rule content
- `hint`: string, optional, rule description
- `priority`: integer, optional, rule ordering priority
- Response: JSON, a single rule
## `PATCH /api/v1/pleroma/admin/rules/:id`
### Update a rule
- Params:
- `text`: string, optional, rule content
- `hint`: string, optional, rule description
- `priority`: integer, optional, rule ordering priority
- Response: JSON, a single rule
## `DELETE /api/v1/pleroma/admin/rules/:id`
### Delete a rule
- Response: JSON, empty object
```json
{}
```

View file

@ -16,13 +16,14 @@ defmodule Pleroma.Rule do
schema "rules" do
field(:priority, :integer, default: 0)
field(:text, :string)
field(:hint, :string)
timestamps()
end
def changeset(%Rule{} = rule, params \\ %{}) do
rule
|> cast(params, [:priority, :text])
|> cast(params, [:priority, :text, :hint])
|> validate_required([:text])
end
@ -39,6 +40,11 @@ def get(ids) when is_list(ids) do
def get(id), do: Repo.get(__MODULE__, id)
def exists?(id) do
from(r in __MODULE__, where: r.id == ^id)
|> Repo.exists?()
end
def create(params) do
{:ok, rule} =
%Rule{}

View file

@ -1340,6 +1340,15 @@ defp restrict_unauthenticated(query, nil) do
defp restrict_unauthenticated(query, _), do: query
defp restrict_rule(query, %{rule_id: rule_id}) do
from(
activity in query,
where: fragment("(?)->'rules' \\? (?)", activity.data, ^rule_id)
)
end
defp restrict_rule(query, _), do: query
defp exclude_poll_votes(query, %{include_poll_votes: true}), do: query
defp exclude_poll_votes(query, _) do
@ -1507,6 +1516,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|> restrict_object(opts)
|> restrict_filtered(opts)
|> restrict_quote_url(opts)
|> restrict_rule(opts)
|> maybe_restrict_deactivated_users(opts)
|> exclude_poll_votes(opts)
|> exclude_chat_messages(opts)

View file

@ -10,8 +10,8 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
alias Pleroma.User
alias Pleroma.Web.AdminAPI
alias Pleroma.Web.AdminAPI.Report
alias Pleroma.Web.AdminAPI.RuleView
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.InstanceView
alias Pleroma.Web.MastodonAPI.StatusView
defdelegate merge_account_views(user), to: AdminAPI.AccountView
@ -92,8 +92,10 @@ defp rules(nil) do
end
defp rules(rule_ids) do
rule_ids
|> Rule.get()
|> render_many(InstanceView, "rule.json", as: :rule)
rules =
rule_ids
|> Rule.get()
render(RuleView, "index.json", rules: rules)
end
end

View file

@ -13,9 +13,10 @@ def render("index.json", %{rules: rules} = _opts) do
def render("show.json", %{rule: rule} = _opts) do
%{
id: rule.id,
id: to_string(rule.id),
priority: rule.priority,
text: rule.text
text: rule.text,
hint: rule.hint
}
end
end

View file

@ -30,6 +30,12 @@ def index_operation do
report_state(),
"Filter by report state"
),
Operation.parameter(
:rule_id,
:query,
%Schema{type: :string},
"Filter by selected rule id"
),
Operation.parameter(
:limit,
:query,
@ -200,8 +206,9 @@ defp report do
items: %Schema{
type: :object,
properties: %{
id: %Schema{type: :integer},
text: %Schema{type: :string}
id: %Schema{type: :string},
text: %Schema{type: :string},
hint: %Schema{type: :string, nullable: true}
}
}
}

View file

@ -84,7 +84,8 @@ defp create_request do
required: [:text],
properties: %{
priority: %Schema{type: :integer},
text: %Schema{type: :string}
text: %Schema{type: :string},
hint: %Schema{type: :string}
}
}
end
@ -94,7 +95,8 @@ defp update_request do
type: :object,
properties: %{
priority: %Schema{type: :integer},
text: %Schema{type: :string}
text: %Schema{type: :string},
hint: %Schema{type: :string}
}
}
end
@ -103,10 +105,10 @@ defp rule do
%Schema{
type: :object,
properties: %{
id: %Schema{type: :integer},
id: %Schema{type: :string},
priority: %Schema{type: :integer},
text: %Schema{type: :string},
created_at: %Schema{type: :string, format: :"date-time"}
hint: %Schema{type: :string, nullable: true}
}
}
end

View file

@ -485,8 +485,9 @@ defp array_of_rules do
items: %Schema{
type: :object,
properties: %{
id: %Schema{type: :integer},
text: %Schema{type: :string}
id: %Schema{type: :string},
text: %Schema{type: :string},
hint: %Schema{type: :string}
}
}
}

View file

@ -57,7 +57,7 @@ defp create_request do
rule_ids: %Schema{
type: :array,
nullable: true,
items: %Schema{type: :number},
items: %Schema{type: :string},
description: "Array of rules"
}
},
@ -67,7 +67,7 @@ defp create_request do
"status_ids" => ["1337"],
"comment" => "bad status!",
"forward" => "false",
"rule_ids" => [3]
"rule_ids" => ["3"]
}
}
end

View file

@ -675,8 +675,7 @@ defp get_report_rules(nil) do
defp get_report_rules(rule_ids) do
rule_ids
|> Rule.get()
|> Enum.map(& &1.id)
|> Enum.filter(&Rule.exists?/1)
end
def update_report_state(activity_ids, state) when is_list(activity_ids) do

View file

@ -91,8 +91,9 @@ def render("rules.json", _) do
def render("rule.json", %{rule: rule}) do
%{
id: rule.id,
text: rule.text
id: to_string(rule.id),
text: rule.text,
hint: rule.hint || ""
}
end
@ -187,10 +188,7 @@ defp common_information(instance) do
title: Keyword.get(instance, :name),
version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.compat_version()})",
languages: Keyword.get(instance, :languages, ["en"]),
rules: render(__MODULE__, "rules.json"),
soapbox: %{
version: Soapbox.version()
}
rules: render(__MODULE__, "rules.json")
}
end

View file

@ -0,0 +1,155 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parser.Card do
alias Pleroma.Web.RichMedia.Parser.Card
alias Pleroma.Web.RichMedia.Parser.Embed
@types ["link", "photo", "video", "rich"]
# https://docs.joinmastodon.org/entities/card/
defstruct url: nil,
title: nil,
description: "",
type: "link",
author_name: "",
author_url: "",
provider_name: "",
provider_url: "",
html: "",
width: 0,
height: 0,
image: nil,
image_description: "",
embed_url: "",
blurhash: nil
def parse(%Embed{url: url, oembed: %{"type" => type, "title" => title} = oembed} = embed)
when type in @types and is_binary(url) do
uri = URI.parse(url)
%Card{
url: url,
title: title,
description: get_description(embed),
type: oembed["type"],
author_name: oembed["author_name"],
author_url: oembed["author_url"],
provider_name: oembed["provider_name"] || uri.host,
provider_url: oembed["provider_url"] || "#{uri.scheme}://#{uri.host}",
html: sanitize_html(oembed["html"]),
width: oembed["width"],
height: oembed["height"],
image: get_image(oembed) |> fix_uri(url) |> proxy(),
image_description: get_image_description(embed),
embed_url: oembed["url"] |> fix_uri(url) |> proxy()
}
|> IO.inspect
|> validate()
end
def parse(%Embed{url: url} = embed) when is_binary(url) do
uri = URI.parse(url)
%Card{
url: url,
title: get_title(embed),
description: get_description(embed),
type: "link",
provider_name: uri.host,
provider_url: "#{uri.scheme}://#{uri.host}",
image: get_image(embed) |> fix_uri(url) |> proxy(),
image_description: get_image_description(embed),
}
|> validate()
end
def parse(card), do: {:error, {:invalid_metadata, card}}
defp get_title(embed) do
case embed do
%{meta: %{"twitter:title" => title}} when is_binary(title) and title != "" -> title
%{meta: %{"og:title" => title}} when is_binary(title) and title != "" -> title
%{title: title} when is_binary(title) and title != "" -> title
_ -> nil
end
end
defp get_description(%{meta: meta}) do
case meta do
%{"twitter:description" => desc} when is_binary(desc) and desc != "" -> desc
%{"og:description" => desc} when is_binary(desc) and desc != "" -> desc
%{"description" => desc} when is_binary(desc) and desc != "" -> desc
_ -> ""
end
end
defp get_image(%{meta: meta}) do
case meta do
%{"twitter:image" => image} when is_binary(image) and image != "" -> image
%{"og:image" => image} when is_binary(image) and image != "" -> image
_ -> ""
end
end
defp get_image(%{"thumbnail_url" => image}) when is_binary(image) and image != "", do: image
defp get_image(%{"type" => "photo", "url" => image}), do: image
defp get_image(_), do: ""
defp get_image_description(%{meta: %{"og:image:alt" => image_description}}), do: image_description
defp get_image_description(_), do: ""
defp sanitize_html(html) do
with {:ok, html} <- FastSanitize.Sanitizer.scrub(html, Pleroma.HTML.Scrubber.OEmbed),
{:ok, [{"iframe", _, _}]} <- Floki.parse_fragment(html) do
html
else
_ -> ""
end
end
def to_map(%Card{} = card) do
card
|> Map.from_struct()
|> stringify_keys()
end
def to_map(%{} = card), do: stringify_keys(card)
defp stringify_keys(%{} = map), do: Map.new(map, fn {k, v} -> {Atom.to_string(k), v} end)
def fix_uri("http://" <> _ = uri, _base_uri), do: uri
def fix_uri("https://" <> _ = uri, _base_uri), do: uri
def fix_uri("/" <> _ = uri, base_uri), do: URI.merge(base_uri, uri) |> URI.to_string()
def fix_uri("", _base_uri), do: nil
def fix_uri(uri, base_uri) when is_binary(uri),
do: URI.merge(base_uri, "/#{uri}") |> URI.to_string()
def fix_uri(_uri, _base_uri), do: nil
defp proxy(url) when is_binary(url), do: Pleroma.Web.MediaProxy.url(url)
defp proxy(_), do: nil
def validate(%Card{type: type, html: html} = card)
when type in ["video", "rich"] and (is_binary(html) == false or html == "") do
card
|> Map.put(:type, "link")
|> validate()
end
def validate(%Card{type: type, title: title} = card)
when type in @types and is_binary(title) and title != "" do
{:ok, card}
end
def validate(%Embed{} = embed) do
case Card.parse(embed) do
{:ok, %Card{} = card} -> validate(card)
card -> {:error, {:invalid_metadata, card}}
end
end
def validate(card), do: {:error, {:invalid_metadata, card}}
end

View file

@ -1,5 +0,0 @@
defmodule Soapbox do
@version "3.0.0"
def version, do: @version
end

View file

@ -1,12 +1,12 @@
defmodule Pleroma.Mixfile do
use Mix.Project
@build_name "soapbox"
@build_name "pl"
def project do
[
app: :pleroma,
name: "Rebased",
name: "pl",
compat_name: "Pleroma",
version: version("2.6.52"),
elixir: "~> 1.11",

View file

@ -0,0 +1,13 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Repo.Migrations.AddHintToRules do
use Ecto.Migration
def change do
alter table(:rules) do
add_if_not_exists(:hint, :text)
end
end
end

View file

@ -11,6 +11,7 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
alias Pleroma.ModerationLog
alias Pleroma.Repo
alias Pleroma.ReportNote
alias Pleroma.Rule
alias Pleroma.Web.CommonAPI
setup do
@ -468,6 +469,34 @@ test "returns 403 when requested by anonymous" do
"error" => "Invalid credentials."
}
end
test "returns reports with specified role_id", %{conn: conn} do
[reporter, target_user] = insert_pair(:user)
%{id: rule_id} = Rule.create(%{text: "Example rule"})
rule_id = to_string(rule_id)
{:ok, %{id: report_id}} =
CommonAPI.report(reporter, %{
account_id: target_user.id,
comment: "",
rule_ids: [rule_id]
})
{:ok, _report} =
CommonAPI.report(reporter, %{
account_id: target_user.id,
comment: ""
})
response =
conn
|> get("/api/pleroma/admin/reports?rule_id=#{rule_id}")
|> json_response_and_validate_schema(:ok)
assert %{"reports" => [%{"id" => ^report_id}]} = response
end
end
describe "POST /api/pleroma/admin/reports/assign_account" do

View file

@ -27,6 +27,10 @@ test "sorts rules by priority", %{conn: conn} do
%{id: id2} = Rule.create(%{text: "Second rule", priority: 2})
%{id: id3} = Rule.create(%{text: "Third rule", priority: 1})
id1 = to_string(id1)
id2 = to_string(id2)
id3 = to_string(id3)
response =
conn
|> get("/api/pleroma/admin/rules")

View file

@ -178,15 +178,17 @@ test "renders included rules" do
user = insert(:user)
other_user = insert(:user)
%{id: id, text: text} = Rule.create(%{text: "Example rule"})
%{id: rule_id, text: text} = Rule.create(%{text: "Example rule"})
rule_id = to_string(rule_id)
{:ok, activity} =
CommonAPI.report(user, %{
account_id: other_user.id,
rule_ids: [id]
rule_ids: [rule_id]
})
assert %{rules: [%{id: ^id, text: ^text}]} =
assert %{rules: [%{id: ^rule_id, text: ^text}]} =
ReportView.render("show.json", Report.extract_report_info(activity))
end
end

View file

@ -58,7 +58,6 @@ test "get instance information", %{conn: conn} do
assert result["pleroma"]["vapid_public_key"]
assert result["pleroma"]["stats"]["mau"] == 0
assert result["pleroma"]["oauth_consumer_strategies"] == []
assert result["soapbox"]["version"] =~ "."
assert email == from_config_email
assert thumbnail == from_config_thumbnail
@ -105,17 +104,28 @@ test "get peers", %{conn: conn} do
end
test "get instance rules", %{conn: conn} do
Rule.create(%{text: "Example rule"})
Rule.create(%{text: "Second rule"})
Rule.create(%{text: "Third rule"})
Rule.create(%{text: "Example rule", hint: "Rule description", priority: 1})
Rule.create(%{text: "Third rule", priority: 2})
Rule.create(%{text: "Second rule", priority: 1})
conn = get(conn, "/api/v1/instance")
assert result = json_response_and_validate_schema(conn, 200)
rules = result["rules"]
assert length(rules) == 3
assert [
%{
"text" => "Example rule",
"hint" => "Rule description"
},
%{
"text" => "Second rule",
"hint" => ""
},
%{
"text" => "Third rule",
"hint" => ""
}
] = result["rules"]
end
test "get instance configuration", %{conn: conn} do

View file

@ -88,6 +88,8 @@ test "submit a report with rule_ids", %{
} do
%{id: rule_id} = Rule.create(%{text: "There are no rules"})
rule_id = to_string(rule_id)
assert %{"action_taken" => false, "id" => id} =
conn
|> put_req_header("content-type", "application/json")
@ -101,6 +103,23 @@ test "submit a report with rule_ids", %{
assert %Activity{data: %{"rules" => [^rule_id]}} = Activity.get_report(id)
end
test "rules field is empty if provided wrong rule id", %{
conn: conn,
target_user: target_user
} do
assert %{"id" => id} =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/reports", %{
"account_id" => target_user.id,
"forward" => "false",
"rule_ids" => ["-1"]
})
|> json_response_and_validate_schema(200)
assert %Activity{data: %{"rules" => []}} = Activity.get_report(id)
end
test "account_id is required", %{
conn: conn,
activity: activity