Merge remote-tracking branch 'origin/develop' into paradedb-search-backend

This commit is contained in:
Mark Felder 2026-03-26 21:29:11 +00:00
commit 9fdc1a8d3b
15 changed files with 196 additions and 6 deletions

View file

@ -0,0 +1 @@
Add immutable tag on cache-control header for several endpoints that's serving the same exact things.

View file

@ -0,0 +1 @@
Federate `votersCount` correctly

View file

@ -29,7 +29,7 @@ defmodule Pleroma.Constants do
const(static_only_files, const(static_only_files,
do: do:
~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc embed.js embed.css) ~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js schemas doc embed.js embed.css)
) )
const(status_updatable_fields, const(status_updatable_fields,

View file

@ -374,10 +374,18 @@ defmodule Pleroma.Object do
voters = [actor | object.data["voters"] || []] |> Enum.uniq() voters = [actor | object.data["voters"] || []] |> Enum.uniq()
voters_count =
if Map.has_key?(object.data, "votersCount") do
object.data["votersCount"] + 1
else
length(voters)
end
data = data =
object.data object.data
|> Map.put(key, options) |> Map.put(key, options)
|> Map.put("voters", voters) |> Map.put("voters", voters)
|> Map.put("votersCount", voters_count)
object object
|> Object.change(%{data: data}) |> Object.change(%{data: data})

View file

@ -12,7 +12,7 @@ defmodule Pleroma.ReverseProxy do
@keep_resp_headers @resp_cache_headers ++ @keep_resp_headers @resp_cache_headers ++
~w(content-length content-type content-disposition content-encoding) ++ ~w(content-length content-type content-disposition content-encoding) ++
~w(content-range accept-ranges vary) ~w(content-range accept-ranges vary)
@default_cache_control_header "public, max-age=1209600" @default_cache_control_header "public, max-age=1209600, immutable"
@valid_resp_codes [200, 206, 304] @valid_resp_codes [200, 206, 304]
@max_read_duration :timer.seconds(30) @max_read_duration :timer.seconds(30)
@max_body_length :infinity @max_body_length :infinity

View file

@ -28,6 +28,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
end end
field(:closed, ObjectValidators.DateTime) field(:closed, ObjectValidators.DateTime)
field(:votersCount, :integer)
field(:voters, {:array, ObjectValidators.ObjectID}, default: []) field(:voters, {:array, ObjectValidators.ObjectID}, default: [])
field(:nonAnonymous, :boolean) field(:nonAnonymous, :boolean)
embeds_many(:anyOf, QuestionOptionsValidator) embeds_many(:anyOf, QuestionOptionsValidator)

View file

@ -783,6 +783,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def set_replies(obj_data), do: obj_data def set_replies(obj_data), do: obj_data
defp set_voters_count(%{"voters" => [_ | _] = voters} = obj) do
Map.merge(obj, %{"votersCount" => length(voters)})
end
defp set_voters_count(obj), do: obj
# Prepares and sanitizes the object for federation. # Prepares and sanitizes the object for federation.
def prepare_object(object) do def prepare_object(object) do
object object
@ -795,6 +801,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> set_reply_to_uri |> set_reply_to_uri
|> set_quote_url |> set_quote_url
|> set_replies |> set_replies
|> set_voters_count
|> CommonFixes.maybe_add_content_map() |> CommonFixes.maybe_add_content_map()
|> strip_internal_fields |> strip_internal_fields
|> strip_internal_tags |> strip_internal_tags

View file

@ -46,8 +46,9 @@ defmodule Pleroma.Web.Endpoint do
plug(Pleroma.Web.Plugs.HTTPSecurityPlug) plug(Pleroma.Web.Plugs.HTTPSecurityPlug)
plug(Pleroma.Web.Plugs.UploadedMedia) plug(Pleroma.Web.Plugs.UploadedMedia)
@static_cache_control "public, max-age=1209600" @static_cache_control "public, max-age=1209600, immutable"
@static_cache_disabled "public, no-cache" @static_cache_disabled "public, no-cache"
@favicon_cache_control "public, max=age=86400, immutable" # cache for a day
# InstanceStatic needs to be before Plug.Static to be able to override shipped-static files # InstanceStatic needs to be before Plug.Static to be able to override shipped-static files
# If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well # If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well
@ -64,6 +65,15 @@ defmodule Pleroma.Web.Endpoint do
} }
) )
plug(Pleroma.Web.Plugs.FaviconPlug,
at: "/",
only: ["favicon.png"],
cache_control_for_etags: @favicon_cache_control,
headers: %{
"cache-control" => @favicon_cache_control
}
)
plug(Pleroma.Web.Plugs.InstanceStatic, plug(Pleroma.Web.Plugs.InstanceStatic,
at: "/", at: "/",
gzip: true, gzip: true,

View file

@ -75,6 +75,8 @@ defmodule Pleroma.Web.MastodonAPI.PollView do
length(voters) length(voters)
end end
defp voters_count(%{data: %{"votersCount" => voters}}), do: voters
defp voters_count(_), do: 0 defp voters_count(_), do: 0
defp voted_and_own_votes(%{object: object} = params, options) do defp voted_and_own_votes(%{object: object} = params, options) do

View file

@ -0,0 +1,71 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2026 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.FaviconPlug do
@behaviour Plug
@moduledoc """
This is a shim to call `Plug.Static` but with runtime `from` configuration for instance favicon.
Serves default or custom favicon.png with cacheable cache-control.
"""
import Plug.Conn, only: [put_resp_header: 3, send_resp: 3, halt: 1]
require Logger
def init(opts) do
opts
|> Keyword.put(:from, "__unconfigured_favicon_static_plug")
|> Plug.Static.init()
end
def call(%{request_path: "/favicon.png"} = conn, opts) do
case find_favicon_dir() do
{:ok, dir} ->
call_static(conn, opts, dir)
:error ->
# Favicon should always be available and this should never occur.
# If it does, halt the pipeline before having unintended side-effects.
Logger.error("No favicon.png found! Is the default favicon deleted?")
conn
|> send_resp(404, "Not found")
|> halt()
end
end
def call(conn, _) do
conn
end
defp find_favicon_dir do
instance_dir = Pleroma.Config.get([:instance, :static_dir], "instance/static")
instance_path = Path.join(instance_dir, "favicon.png")
priv_dir = Application.app_dir(:pleroma, "priv/static")
priv_path = Path.join(priv_dir, "favicon.png")
cond do
File.exists?(instance_path) -> {:ok, instance_dir}
File.exists?(priv_path) -> {:ok, priv_dir}
true -> :error
end
end
defp call_static(conn, opts, from) do
opts =
opts
|> Map.put(:from, from)
|> Map.put(:content_types, false)
conn = set_content_type(conn)
Plug.Static.call(conn, opts)
end
defp set_content_type(conn) do
put_resp_header(conn, "content-type", "image/png")
end
end

View file

@ -17,7 +17,7 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do
# no slashes # no slashes
@path "media" @path "media"
@default_cache_control_header "public, max-age=1209600" @default_cache_control_header "public, max-age=1209600, immutable"
def init(_opts) do def init(_opts) do
static_plug_opts = static_plug_opts =

View file

@ -294,7 +294,7 @@ defmodule Pleroma.ReverseProxyTest do
|> expect(:stream_body, fn _ -> :done end) |> expect(:stream_body, fn _ -> :done end)
conn = ReverseProxy.call(conn, "/cache") conn = ReverseProxy.call(conn, "/cache")
assert {"cache-control", "public, max-age=1209600"} in conn.resp_headers assert {"cache-control", "public, max-age=1209600, immutable"} in conn.resp_headers
end end
end end

View file

@ -170,4 +170,23 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.QuestionHandlingTest do
assert {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data) assert {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data)
end end
test "it displays voters count for a poll" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} =
CommonAPI.post(user, %{
status: "???",
poll: %{expires_in: 10, options: ["yes", "no"]}
})
object = Object.normalize(activity, fetch: false)
{:ok, _, _} = CommonAPI.vote(object, other_user, [1])
{:ok, modified} = Transmogrifier.prepare_activity(activity.data)
refute Map.has_key?(modified["object"], "voters")
assert modified["object"]["votersCount"] == 1
end
end end

View file

@ -167,7 +167,14 @@ defmodule Pleroma.Web.MastodonAPI.PollViewTest do
} = PollView.render("show.json", %{object: object}) } = PollView.render("show.json", %{object: object})
end end
test "that poll is non anonymous" do test "displays correct voters count" do
object = Object.normalize("https://friends.grishka.me/posts/54642", fetch: true)
result = PollView.render("show.json", %{object: object})
assert result[:voters_count] == 14
end
test "detects that poll is non anonymous" do
object = Object.normalize("https://friends.grishka.me/posts/54642", fetch: true) object = Object.normalize("https://friends.grishka.me/posts/54642", fetch: true)
result = PollView.render("show.json", %{object: object}) result = PollView.render("show.json", %{object: object})

View file

@ -0,0 +1,63 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2026 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.FaviconPlugTest do
use Pleroma.Web.ConnCase
@dir "test/tmp/favicon_static"
setup do
Pleroma.Backports.mkdir_p!(@dir)
on_exit(fn -> File.rm_rf!(@dir) end)
end
describe "default favicon" do
test "returns favicon", %{conn: conn} do
conn = get(conn, "/favicon.png")
body_size = byte_size(conn.resp_body)
assert conn.status == 200
assert body_size == 1583
assert response_content_type(conn, :png)
end
test "returns correct cache-control", %{conn: conn} do
conn = get(conn, "/favicon.png")
cache = get_resp_header(conn, "cache-control")
assert conn.status == 200
assert cache == ["public, max=age=86400, immutable"]
end
end
describe "custom favicon" do
setup do
favicon_path = Path.join(@dir, "favicon.png")
donor_image = "test/fixtures/image.png"
File.cp!(donor_image, favicon_path)
clear_config([:instance, :static_dir], @dir)
on_exit(fn -> File.rm!(favicon_path) end)
end
test "returns favicon", %{conn: conn} do
conn = get(conn, "/favicon.png")
body_size = byte_size(conn.resp_body)
assert conn.status == 200
assert body_size == 104_426
assert response_content_type(conn, :png)
end
test "returns correct cache-control", %{conn: conn} do
conn = get(conn, "/favicon.png")
cache = get_resp_header(conn, "cache-control")
assert conn.status == 200
assert cache == ["public, max=age=86400, immutable"]
end
end
end