Add ability to set a default post expiry (#321)
authorfloatingghost <hannah@coffee-and-dreams.uk>
Mon, 28 Nov 2022 13:34:54 +0000 (13:34 +0000)
committerfloatingghost <hannah@coffee-and-dreams.uk>
Mon, 28 Nov 2022 13:34:54 +0000 (13:34 +0000)
Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma/pulls/321

13 files changed:
CHANGELOG.md
lib/pleroma/user.ex
lib/pleroma/web/api_spec/operations/account_operation.ex
lib/pleroma/web/api_spec/schemas/account.ex
lib/pleroma/web/mastodon_api/controllers/account_controller.ex
lib/pleroma/web/mastodon_api/controllers/status_controller.ex
lib/pleroma/web/mastodon_api/views/account_view.ex
lib/pleroma/web/mastodon_api/views/status_view.ex
priv/repo/migrations/20220905011454_generate_unset_user_keys.exs
priv/repo/migrations/20221128103145_add_per_user_post_expiry.exs [new file with mode: 0644]
test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
test/pleroma/web/mastodon_api/update_credentials_test.exs
test/pleroma/web/mastodon_api/views/account_view_test.exs

index b6eb928c6a1d7bd90825315cc139729c5110e480..b49f77428b514f96d613d7c42f92fec3f4255db6 100644 (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)
index eb907a2d86340d43caef83fa7a6e8a7f9182de6c..b0ab9d0cd6ada61835b7bad09019524b9edee2ef 100644 (file)
@@ -151,6 +151,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,
@@ -516,7 +517,8 @@ defmodule Pleroma.User do
         :pleroma_settings_store,
         :is_discoverable,
         :actor_type,
-        :disclose_client
+        :disclose_client,
+        :status_ttl_days
       ]
     )
     |> unique_constraint(:nickname)
@@ -524,6 +526,7 @@ defmodule Pleroma.User 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)})
index 359a73ac06e3ddcd45914d61f3835e902d67a077..e20f57fec3e7bed9adbb565587344144f572406c 100644 (file)
@@ -700,7 +700,13 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation 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,
@@ -720,7 +726,8 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation 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
index 5d3ac9cd0dd3c82c75c9348b77d862e4a69c06dd..2693eaceb90cffb9dbd0b2ee7266cad70dae3abf 100644 (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{
         type: :object,
         properties: %{
index 946c8544fe2fbd64a5c8e38598816cecf3fd5f27..80497a2527ea2eb2f8d7d2dc66cd231e3353f2a1 100644 (file)
@@ -175,6 +175,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
       value -> {:ok, value}
     end
 
+    status_ttl_days_value = fn
+      -1 -> {:ok, nil}
+      value -> {:ok, value}
+    end
+
     user_params =
       [
         :no_rich_text,
@@ -215,7 +220,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
       # 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
index 31f3b3a8de095d7892cbc23505f3b960f2e9835e..338a35052ae9fb7331daea2a97d9d6326ff3d76f 100644 (file)
@@ -171,6 +171,16 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
       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,
index a04ffaaf3a7d9d85a069f635ce16769f8efc0cf1..653a50e20aecc3a4b76eb70a16a56d8814399ccd 100644 (file)
@@ -287,7 +287,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView 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
index 929641b8454f0d6fcafe0cf0dd336831a221eb78..cc58f803e6b2c7773c96f6b1ad21d7ce0f7830ee 100644 (file)
@@ -65,7 +65,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView 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))
index 43bc7100bd639a3b61a79df549b70ff894dbc0f9..3b04bb4f3e88600f9aebdf091c0ef58ad8ba86c1 100644 (file)
@@ -14,14 +14,14 @@ defmodule Pleroma.Repo.Migrations.GenerateUnsetUserKeys 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
diff --git a/priv/repo/migrations/20221128103145_add_per_user_post_expiry.exs b/priv/repo/migrations/20221128103145_add_per_user_post_expiry.exs
new file mode 100644 (file)
index 0000000..95c585c
--- /dev/null
@@ -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
index ea6ace69f5ac60529e6881b22651be1511d85725..cda4c9e03bedb093746870969c22a980b5acbdae 100644 (file)
@@ -126,6 +126,35 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest 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
index 606467fa97a1436c3dfba47bf7d6e8eb18e362b0..435782d0ac58e592c3b1206fbba8685156621c67 100644 (file)
@@ -209,6 +209,26 @@ defmodule Pleroma.Web.MastodonAPI.UpdateCredentialsTest 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", %{
index f4a5f4d50326673eec6e9549e5e0145aa85526a5..c9036d67d225165ddde7bc08572412c3be162b0d 100644 (file)
@@ -37,7 +37,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest 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 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest 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",
@@ -243,7 +245,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
           name: "localhost",
           favicon: "http://localhost:4001/favicon.png",
           nodeinfo: %{version: "2.0"}
-        }
+        },
+        status_ttl_days: nil
       },
       pleroma: %{
         ap_id: user.ap_id,