Add ability to set a default post expiry (#321)
Some checks are pending
ci/woodpecker/push/woodpecker Pipeline is pending

Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: #321
This commit is contained in:
floatingghost 2022-11-28 13:34:54 +00:00
parent 2d019e14e3
commit 0cfd5b4e89
13 changed files with 107 additions and 11 deletions

View file

@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Added ## Added
- Config: HTTP timeout options, :pool\_timeout and :receive\_timeout - Config: HTTP timeout options, :pool\_timeout and :receive\_timeout
- Added statistic gathering about instances which do/don't have signed fetches when they request from us - Added statistic gathering about instances which do/don't have signed fetches when they request from us
- Ability to set a default post expiry time, after which the post will be deleted. If used in concert with ActivityExpiration MRF, the expiry which comes _sooner_ will be applied.
## Changed ## Changed
- MastoAPI: Accept BooleanLike input on `/api/v1/accounts/:id/follow` (fixes follows with mastodon.py) - MastoAPI: Accept BooleanLike input on `/api/v1/accounts/:id/follow` (fixes follows with mastodon.py)

View file

@ -151,6 +151,7 @@ defmodule Pleroma.User do
field(:is_suggested, :boolean, default: false) field(:is_suggested, :boolean, default: false)
field(:last_status_at, :naive_datetime) field(:last_status_at, :naive_datetime)
field(:language, :string) field(:language, :string)
field(:status_ttl_days, :integer, default: nil)
embeds_one( embeds_one(
:notification_settings, :notification_settings,
@ -516,7 +517,8 @@ def update_changeset(struct, params \\ %{}) do
:pleroma_settings_store, :pleroma_settings_store,
:is_discoverable, :is_discoverable,
:actor_type, :actor_type,
:disclose_client :disclose_client,
:status_ttl_days
] ]
) )
|> unique_constraint(:nickname) |> unique_constraint(:nickname)
@ -524,6 +526,7 @@ def update_changeset(struct, params \\ %{}) do
|> validate_length(:bio, max: bio_limit) |> validate_length(:bio, max: bio_limit)
|> validate_length(:name, min: 1, max: name_limit) |> validate_length(:name, min: 1, max: name_limit)
|> validate_inclusion(:actor_type, ["Person", "Service"]) |> validate_inclusion(:actor_type, ["Person", "Service"])
|> validate_number(:status_ttl_days, greater_than: 0)
|> put_fields() |> put_fields()
|> put_emoji() |> put_emoji()
|> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)}) |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})

View file

@ -700,7 +700,13 @@ defp update_credentials_request do
description: description:
"Discovery (listing, indexing) of this account by external services (search bots etc.) is allowed." "Discovery (listing, indexing) of this account by external services (search bots etc.) is allowed."
}, },
actor_type: ActorType actor_type: ActorType,
status_ttl_days: %Schema{
type: :integer,
nullable: true,
description:
"Number of days after which statuses will be deleted. Set to -1 to disable."
}
}, },
example: %{ example: %{
bot: false, bot: false,
@ -720,7 +726,8 @@ defp update_credentials_request do
allow_following_move: false, allow_following_move: false,
also_known_as: ["https://foo.bar/users/foo"], also_known_as: ["https://foo.bar/users/foo"],
discoverable: false, discoverable: false,
actor_type: "Person" actor_type: "Person",
status_ttl_days: 30
} }
} }
end end

View file

@ -109,6 +109,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
} }
} }
}, },
akkoma: %Schema{
type: :object,
properties: %{
note_ttl_days: %Schema{type: :integer}
}
},
source: %Schema{ source: %Schema{
type: :object, type: :object,
properties: %{ properties: %{

View file

@ -175,6 +175,11 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
value -> {:ok, value} value -> {:ok, value}
end end
status_ttl_days_value = fn
-1 -> {:ok, nil}
value -> {:ok, value}
end
user_params = user_params =
[ [
:no_rich_text, :no_rich_text,
@ -215,7 +220,9 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
# Note: param name is indeed :discoverable (not an error) # Note: param name is indeed :discoverable (not an error)
|> Maps.put_if_present(:is_discoverable, params[:discoverable]) |> Maps.put_if_present(:is_discoverable, params[:discoverable])
|> Maps.put_if_present(:language, Pleroma.Web.Gettext.normalize_locale(params[:language])) |> Maps.put_if_present(:language, Pleroma.Web.Gettext.normalize_locale(params[:language]))
|> Maps.put_if_present(:status_ttl_days, params[:status_ttl_days], status_ttl_days_value)
IO.inspect(user_params)
# What happens here: # What happens here:
# #
# We want to update the user through the pipeline, but the ActivityPub # We want to update the user through the pipeline, but the ActivityPub

View file

@ -171,6 +171,16 @@ def create(%{assigns: %{user: user}, body_params: %{status: _} = params} = conn,
Map.put(params, :in_reply_to_status_id, params[:in_reply_to_id]) Map.put(params, :in_reply_to_status_id, params[:in_reply_to_id])
|> put_application(conn) |> put_application(conn)
expires_in_seconds =
if is_nil(user.status_ttl_days),
do: nil,
else: 60 * 60 * 24 * user.status_ttl_days
params =
if is_nil(expires_in_seconds),
do: params,
else: Map.put(params, :expires_in, expires_in_seconds)
with {:ok, activity} <- CommonAPI.post(user, params) do with {:ok, activity} <- CommonAPI.post(user, params) do
try_render(conn, "show.json", try_render(conn, "show.json",
activity: activity, activity: activity,

View file

@ -287,7 +287,8 @@ defp do_render("show.json", %{user: user} = opts) do
}, },
last_status_at: user.last_status_at, last_status_at: user.last_status_at,
akkoma: %{ akkoma: %{
instance: render("instance.json", %{instance: instance}) instance: render("instance.json", %{instance: instance}),
status_ttl_days: user.status_ttl_days
}, },
# Pleroma extensions # Pleroma extensions
# Note: it's insecure to output :email but fully-qualified nickname may serve as safe stub # Note: it's insecure to output :email but fully-qualified nickname may serve as safe stub

View file

@ -65,7 +65,7 @@ defp get_replied_to_activities(activities) do
# This should be removed in a future version of Pleroma. Pleroma-FE currently # This should be removed in a future version of Pleroma. Pleroma-FE currently
# depends on this field, as well. # depends on this field, as well.
defp get_context_id(%{data: %{"context" => context}}) when is_binary(context) do defp get_context_id(%{data: %{"context" => context}}) when is_binary(context) do
use Bitwise import Bitwise
:erlang.crc32(context) :erlang.crc32(context)
|> band(bnot(0x8000_0000)) |> band(bnot(0x8000_0000))

View file

@ -14,14 +14,14 @@ def change do
from(u in User, from(u in User,
where: u.local == true, where: u.local == true,
where: is_nil(u.keys), where: is_nil(u.keys),
select: u select: u.id
) )
Repo.stream(query) Repo.stream(query)
|> Enum.each(fn user -> |> Enum.each(fn user ->
with {:ok, pem} <- Keys.generate_rsa_pem() do with {:ok, pem} <- Keys.generate_rsa_pem() do
Ecto.Changeset.cast(user, %{keys: pem}, [:keys]) Ecto.Changeset.cast(%User{id: user}, %{keys: pem}, [:keys])
|> Repo.update() |> Repo.update(returning: false)
end end
end) end)
end end

View file

@ -0,0 +1,9 @@
defmodule Pleroma.Repo.Migrations.AddPerUserPostExpiry do
use Ecto.Migration
def change do
alter table(:users) do
add(:status_ttl_days, :integer, null: true)
end
end
end

View file

@ -126,6 +126,35 @@ test "posting a status", %{conn: conn} do
) )
end end
test "automatically setting a post expiry if status_ttl_days is set" do
user = insert(:user, status_ttl_days: 1)
%{user: _user, token: _token, conn: conn} = oauth_access(["write:statuses"], user: user)
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("api/v1/statuses", %{
"status" => "aa chikichiki banban"
})
assert %{"id" => id} = json_response_and_validate_schema(conn, 200)
activity = Activity.get_by_id_with_object(id)
{:ok, expires_at, _} = DateTime.from_iso8601(activity.data["expires_at"])
assert Timex.diff(
expires_at,
DateTime.utc_now(),
:hours
) == 23
assert_enqueued(
worker: Pleroma.Workers.PurgeExpiredActivity,
args: %{activity_id: id},
scheduled_at: DateTime.add(DateTime.utc_now(), 1 * 60 * 60 * 24)
)
end
test "it fails to create a status if `expires_in` is less or equal than an hour", %{ test "it fails to create a status if `expires_in` is less or equal than an hour", %{
conn: conn conn: conn
} do } do

View file

@ -209,6 +209,26 @@ test "updates the user's name", %{conn: conn} do
assert update_activity.data["object"]["name"] == "markorepairs" assert update_activity.data["object"]["name"] == "markorepairs"
end end
test "updates the user's default post expiry", %{conn: conn} do
conn = patch(conn, "/api/v1/accounts/update_credentials", %{"status_ttl_days" => "1"})
assert user_data = json_response_and_validate_schema(conn, 200)
assert user_data["akkoma"]["status_ttl_days"] == 1
end
test "resets the user's default post expiry", %{conn: conn} do
conn = patch(conn, "/api/v1/accounts/update_credentials", %{"status_ttl_days" => "-1"})
assert user_data = json_response_and_validate_schema(conn, 200)
assert is_nil(user_data["akkoma"]["status_ttl_days"])
end
test "does not allow negative integers other than -1 for TTL", %{conn: conn} do
conn = patch(conn, "/api/v1/accounts/update_credentials", %{"status_ttl_days" => "-2"})
assert user_data = json_response_and_validate_schema(conn, 403)
end
test "updates the user's AKAs", %{conn: conn} do test "updates the user's AKAs", %{conn: conn} do
conn = conn =
patch(conn, "/api/v1/accounts/update_credentials", %{ patch(conn, "/api/v1/accounts/update_credentials", %{

View file

@ -37,7 +37,8 @@ test "Represent a user account" do
inserted_at: ~N[2017-08-15 15:47:06.597036], inserted_at: ~N[2017-08-15 15:47:06.597036],
emoji: %{"karjalanpiirakka" => "/file.png"}, emoji: %{"karjalanpiirakka" => "/file.png"},
raw_bio: "valid html. a\nb\nc\nd\nf '&<>\"", raw_bio: "valid html. a\nb\nc\nd\nf '&<>\"",
also_known_as: ["https://shitposter.zone/users/shp"] also_known_as: ["https://shitposter.zone/users/shp"],
status_ttl_days: 5
}) })
insert(:instance, %{host: "example.com", nodeinfo: %{version: "2.1"}}) insert(:instance, %{host: "example.com", nodeinfo: %{version: "2.1"}})
@ -61,7 +62,8 @@ test "Represent a user account" do
"version" => "2.1" "version" => "2.1"
}, },
favicon: nil favicon: nil
} },
status_ttl_days: 5
}, },
avatar: "http://localhost:4001/images/avi.png", avatar: "http://localhost:4001/images/avi.png",
avatar_static: "http://localhost:4001/images/avi.png", avatar_static: "http://localhost:4001/images/avi.png",
@ -243,7 +245,8 @@ test "Represent a Service(bot) account" do
name: "localhost", name: "localhost",
favicon: "http://localhost:4001/favicon.png", favicon: "http://localhost:4001/favicon.png",
nodeinfo: %{version: "2.0"} nodeinfo: %{version: "2.0"}
} },
status_ttl_days: nil
}, },
pleroma: %{ pleroma: %{
ap_id: user.ap_id, ap_id: user.ap_id,