Add support for activity expiration to common and Masto API
authorMike Verdone <spiral@arcseconds.net>
Mon, 22 Jul 2019 14:46:20 +0000 (16:46 +0200)
committerMike Verdone <spiral@arcseconds.net>
Wed, 24 Jul 2019 12:45:14 +0000 (14:45 +0200)
The "expires_at" parameter accepts an ISO8601-formatted date which
defines when the activity will expire.

At this point the API will not give you any feedback about if your post
will expire or not.

docs/api/differences_in_mastoapi_responses.md
lib/pleroma/activity_expiration.ex
lib/pleroma/web/common_api/common_api.ex
test/support/factory.ex
test/web/common_api/common_api_test.exs
test/web/mastodon_api/mastodon_api_controller_test.exs

index 1907d70c809ec41fa4e36abf9549de054956cb8c..7d5be47139a1162da65a1d77e46fcb1ffbdd6c0b 100644 (file)
@@ -79,6 +79,7 @@ Additional parameters can be added to the JSON body/Form data:
 - `content_type`: string, contain the MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint.
 - `to`: A list of nicknames (like `lain@soykaf.club` or `lain` on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the `status` body, only the people in the `to` list will be addressed. The normal rules for for post visibility are not affected by this and will still apply.
 - `visibility`: string, besides standard MastoAPI values (`direct`, `private`, `unlisted` or `public`) it can be used to address a List by setting it to `list:LIST_ID`.
+- `expires_on`: datetime (iso8601), sets when the posted activity should expire. When a posted activity expires it will be deleted from the server, and a delete request for it will be federated.
 
 ## PATCH `/api/v1/update_credentials`
 
index d3d95f9e94ec5cad4d02b6ff4a99d1f10e9cec6c..a0af5255b7ab8415a3bf06ea32e7fe361f119687 100644 (file)
@@ -10,6 +10,7 @@ defmodule Pleroma.ActivityExpiration do
   alias Pleroma.FlakeId
   alias Pleroma.Repo
 
+  import Ecto.Changeset
   import Ecto.Query
 
   @type t :: %__MODULE__{}
@@ -19,6 +20,24 @@ defmodule Pleroma.ActivityExpiration do
     field(:scheduled_at, :naive_datetime)
   end
 
+  def changeset(%ActivityExpiration{} = expiration, attrs) do
+    expiration
+    |> cast(attrs, [:scheduled_at])
+    |> validate_required([:scheduled_at])
+  end
+
+  def get_by_activity_id(activity_id) do
+    ActivityExpiration
+    |> where([exp], exp.activity_id == ^activity_id)
+    |> Repo.one()
+  end
+
+  def create(%Activity{} = activity, scheduled_at) do
+    %ActivityExpiration{activity_id: activity.id}
+    |> changeset(%{scheduled_at: scheduled_at})
+    |> Repo.insert()
+  end
+
   def due_expirations(offset \\ 0) do
     naive_datetime =
       NaiveDateTime.utc_now()
index 44af6a77341de80897b78b29e0f73ae04c7dc6fc..0f287af4eb240f84d9d6deef5ded0b3e58bb6597 100644 (file)
@@ -4,6 +4,7 @@
 
 defmodule Pleroma.Web.CommonAPI do
   alias Pleroma.Activity
+  alias Pleroma.ActivityExpiration
   alias Pleroma.Formatter
   alias Pleroma.Object
   alias Pleroma.ThreadMute
@@ -218,6 +219,7 @@ defmodule Pleroma.Web.CommonAPI do
          context <- make_context(in_reply_to),
          cw <- data["spoiler_text"] || "",
          sensitive <- data["sensitive"] || Enum.member?(tags, {"#nsfw", "nsfw"}),
+         {:ok, expires_at} <- Ecto.Type.cast(:naive_datetime, data["expires_at"]),
          full_payload <- String.trim(status <> cw),
          :ok <- validate_character_limit(full_payload, attachments, limit),
          object <-
@@ -243,15 +245,24 @@ defmodule Pleroma.Web.CommonAPI do
       preview? = Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false
       direct? = visibility == "direct"
 
-      %{
-        to: to,
-        actor: user,
-        context: context,
-        object: object,
-        additional: %{"cc" => cc, "directMessage" => direct?}
-      }
-      |> maybe_add_list_data(user, visibility)
-      |> ActivityPub.create(preview?)
+      result =
+        %{
+          to: to,
+          actor: user,
+          context: context,
+          object: object,
+          additional: %{"cc" => cc, "directMessage" => direct?}
+        }
+        |> maybe_add_list_data(user, visibility)
+        |> ActivityPub.create(preview?)
+
+      if expires_at do
+        with {:ok, activity} <- result do
+          ActivityExpiration.create(activity, expires_at)
+        end
+      end
+
+      result
     else
       {:private_to_public, true} ->
         {:error, dgettext("errors", "The message visibility must be direct")}
index 7b52b13283fde34f4f5818663c6b2653b1372c14..63fe3a66d30a5a25256e2ee9d48eeb52d9dd7c3a 100644 (file)
@@ -143,12 +143,14 @@ defmodule Pleroma.Factory do
   end
 
   defp expiration_offset_by_minutes(attrs, minutes) do
+    scheduled_at =
+      NaiveDateTime.utc_now()
+      |> NaiveDateTime.add(:timer.minutes(minutes), :millisecond)
+      |> NaiveDateTime.truncate(:second)
+
     %Pleroma.ActivityExpiration{}
     |> Map.merge(attrs)
-    |> Map.put(
-      :scheduled_at,
-      NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(minutes), :millisecond)
-    )
+    |> Map.put(:scheduled_at, scheduled_at)
   end
 
   def expiration_in_the_past_factory(attrs \\ %{}) do
index 16b3f121d52caf83eb245e7635f394b2e687cf16..210314a4a3dea44d4a37e2972f93356fa93234d5 100644 (file)
@@ -160,6 +160,23 @@ defmodule Pleroma.Web.CommonAPITest do
 
       Pleroma.Config.put([:instance, :limit], limit)
     end
+
+    test "it can handle activities that expire" do
+      user = insert(:user)
+
+      expires_at =
+        NaiveDateTime.utc_now()
+        |> NaiveDateTime.truncate(:second)
+        |> NaiveDateTime.add(1_000_000, :second)
+
+      expires_at_iso8601 = expires_at |> NaiveDateTime.to_iso8601()
+
+      assert {:ok, activity} =
+               CommonAPI.post(user, %{"status" => "chai", "expires_at" => expires_at_iso8601})
+
+      assert expiration = Pleroma.ActivityExpiration.get_by_activity_id(activity.id)
+      assert expiration.scheduled_at == expires_at
+    end
   end
 
   describe "reactions" do
index b5279412f64cb28827dc5810c2ab869982d0515b..24482a4a27f99f6b7c81a359fdf7c5d62cde72af 100644 (file)
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
 
   alias Ecto.Changeset
   alias Pleroma.Activity
+  alias Pleroma.ActivityExpiration
   alias Pleroma.Notification
   alias Pleroma.Object
   alias Pleroma.Repo
@@ -151,6 +152,24 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
 
       assert %{"id" => third_id} = json_response(conn_three, 200)
       refute id == third_id
+
+      # An activity that will expire:
+      expires_at =
+        NaiveDateTime.utc_now()
+        |> NaiveDateTime.add(:timer.minutes(120), :millisecond)
+        |> NaiveDateTime.truncate(:second)
+
+      conn_four =
+        conn
+        |> post("api/v1/statuses", %{
+          "status" => "oolong",
+          "expires_at" => expires_at
+        })
+
+      assert %{"id" => fourth_id} = json_response(conn_four, 200)
+      assert activity = Activity.get_by_id(fourth_id)
+      assert expiration = ActivityExpiration.get_by_activity_id(fourth_id)
+      assert expiration.scheduled_at == expires_at
     end
 
     test "replying to a status", %{conn: conn} do