Add ability to set a default post expiry (#321)

Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma/pulls/321
This commit is contained in:
floatingghost 2022-11-28 13:34:54 +00:00 committed by Sam Therapy
parent 8007c756f8
commit 8ee97f6989
Signed by: sam
GPG key ID: 4D8B07C18F31ACBD
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
- 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
- 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
- MastoAPI: Accept BooleanLike input on `/api/v1/accounts/:id/follow` (fixes follows with mastodon.py)

View file

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

View file

@ -705,7 +705,13 @@ defp update_credentials_request do
description:
"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: %{
bot: false,
@ -725,7 +731,8 @@ defp update_credentials_request do
allow_following_move: false,
also_known_as: ["https://foo.bar/users/foo"],
discoverable: false,
actor_type: "Person"
actor_type: "Person",
status_ttl_days: 30
}
}
end

View file

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

View file

@ -178,6 +178,11 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
value -> {:ok, value}
end
status_ttl_days_value = fn
-1 -> {:ok, nil}
value -> {:ok, value}
end
user_params =
[
:no_rich_text,
@ -219,7 +224,9 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
# Note: param name is indeed :discoverable (not an error)
|> Maps.put_if_present(:is_discoverable, params[:discoverable])
|> 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:
#
# We want to update the user through the pipeline, but the ActivityPub

View file

@ -172,6 +172,16 @@ def create(%{assigns: %{user: user}, body_params: %{status: _} = params} = conn,
Map.put(params, :in_reply_to_status_id, params[:in_reply_to_id])
|> 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
try_render(conn, "show.json",
activity: activity,

View file

@ -287,7 +287,8 @@ defp do_render("show.json", %{user: user} = opts) do
},
last_status_at: user.last_status_at,
akkoma: %{
instance: render("instance.json", %{instance: instance})
instance: render("instance.json", %{instance: instance}),
status_ttl_days: user.status_ttl_days
},
# Pleroma extensions
# 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
# depends on this field, as well.
defp get_context_id(%{data: %{"context" => context}}) when is_binary(context) do
use Bitwise
import Bitwise
:erlang.crc32(context)
|> band(bnot(0x8000_0000))

View file

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

View file

@ -216,6 +216,26 @@ test "updates the user's name", %{conn: conn} do
assert update_activity.data["object"]["name"] == "markorepairs"
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
conn =
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],
emoji: %{"karjalanpiirakka" => "/file.png"},
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"}})
@ -61,7 +62,8 @@ test "Represent a user account" do
"version" => "2.1"
},
favicon: nil
}
},
status_ttl_days: 5
},
avatar: "http://localhost:4001/images/avi.png",
avatar_static: "http://localhost:4001/images/avi.png",
@ -244,7 +246,8 @@ test "Represent a Service(bot) account" do
name: "localhost",
favicon: "http://localhost:4001/favicon.png",
nodeinfo: %{version: "2.0"}
}
},
status_ttl_days: nil
},
pleroma: %{
ap_id: user.ap_id,