Merge branch 'feature/pinned-posts' into 'develop'
authorkaniini <nenolod@gmail.com>
Thu, 10 Jan 2019 02:39:53 +0000 (02:39 +0000)
committerkaniini <nenolod@gmail.com>
Thu, 10 Jan 2019 02:39:53 +0000 (02:39 +0000)
Pinned Statuses

Closes #440

See merge request pleroma/pleroma!636

19 files changed:
config/config.exs
docs/config.md
lib/pleroma/user/info.ex
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/common_api/common_api.ex
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
lib/pleroma/web/mastodon_api/views/status_view.ex
lib/pleroma/web/router.ex
lib/pleroma/web/twitter_api/representers/activity_representer.ex
lib/pleroma/web/twitter_api/twitter_api.ex
lib/pleroma/web/twitter_api/twitter_api_controller.ex
lib/pleroma/web/twitter_api/views/activity_view.ex
test/web/activity_pub/activity_pub_test.exs
test/web/common_api/common_api_test.exs
test/web/mastodon_api/mastodon_api_controller_test.exs
test/web/mastodon_api/status_view_test.exs
test/web/twitter_api/representers/activity_representer_test.exs
test/web/twitter_api/twitter_api_controller_test.exs
test/web/twitter_api/views/activity_view_test.exs

index 9f0f4cd4f81cc636d6e7cc9df816dca8b0db6aec..1c55807b7cfdbf629262c4dae2bdfb7b33329ec4 100644 (file)
@@ -138,7 +138,8 @@ config :pleroma, :instance,
   ],
   finmoji_enabled: true,
   mrf_transparency: true,
-  autofollowed_nicknames: []
+  autofollowed_nicknames: [],
+  max_pinned_statuses: 1
 
 config :pleroma, :markup,
   # XXX - unfortunately, inline images must be enabled by default right now, because
index 1a9706f5b8fe1dbddfca60b15fe7a74b300d42d0..7eb2d567125f3c0f5993e9d02347d6b3d449d822 100644 (file)
@@ -32,7 +32,7 @@ This filter replaces the filename (not the path) of an upload. For complete obfu
 
 ## Pleroma.Mailer
 * `adapter`: one of the mail adapters listed in [Swoosh readme](https://github.com/swoosh/swoosh#adapters), or `Swoosh.Adapters.Local` for in-memory mailbox.
-* `api_key` / `password` and / or other adapter-specific settings, per the above documentation. 
+* `api_key` / `password` and / or other adapter-specific settings, per the above documentation.
 
 An example for Sendgrid adapter:
 
@@ -93,6 +93,7 @@ config :pleroma, Pleroma.Mailer,
 * `always_show_subject_input`: When set to false, auto-hide the subject field when it's empty.
 * `extended_nickname_format`: Set to `true` to use extended local nicknames format (allows underscores/dashes). This will break federation with
     older software for theses nicknames.
+* `max_pinned_statuses`: The maximum number of pinned statuses. `0` will disable the feature.
 * `autofollowed_nicknames`: Set to nicknames of (local) users that every new user should automatically follow.
 
 ## :logger
index 7c79dfcff32984ccf6016cc28f4350d1f566a978..fb1791c205d8874ecb33126f54768f637dca004c 100644 (file)
@@ -31,6 +31,7 @@ defmodule Pleroma.User.Info do
     field(:hub, :string, default: nil)
     field(:salmon, :string, default: nil)
     field(:hide_network, :boolean, default: false)
+    field(:pinned_activities, {:array, :integer}, default: [])
 
     # Found in the wild
     # ap_id -> Where is this used?
@@ -196,4 +197,26 @@ defmodule Pleroma.User.Info do
       :is_admin
     ])
   end
+
+  def add_pinnned_activity(info, %Pleroma.Activity{id: id}) do
+    if id not in info.pinned_activities do
+      max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
+      params = %{pinned_activities: info.pinned_activities ++ [id]}
+
+      info
+      |> cast(params, [:pinned_activities])
+      |> validate_length(:pinned_activities,
+        max: max_pinned_statuses,
+        message: "You have already pinned the maximum number of statuses"
+      )
+    else
+      change(info)
+    end
+  end
+
+  def remove_pinnned_activity(info, %Pleroma.Activity{id: id}) do
+    params = %{pinned_activities: List.delete(info.pinned_activities, id)}
+
+    cast(info, params, [:pinned_activities])
+  end
 end
index b8141146fa5372a3c275e99cf66267de9a533533..9c1eb377f48b6e5b644038f70f69a0cbcb1d3f9e 100644 (file)
@@ -391,6 +391,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
       |> Map.put("type", ["Create", "Announce"])
       |> Map.put("actor_id", user.ap_id)
       |> Map.put("whole_db", true)
+      |> Map.put("pinned_activity_ids", user.info.pinned_activities)
 
     recipients =
       if reading_user do
@@ -549,6 +550,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     )
   end
 
+  defp restrict_pinned(query, %{"pinned" => "true", "pinned_activity_ids" => ids}) do
+    from(activity in query, where: activity.id in ^ids)
+  end
+
+  defp restrict_pinned(query, _), do: query
+
   def fetch_activities_query(recipients, opts \\ %{}) do
     base_query =
       from(
@@ -573,6 +580,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     |> restrict_visibility(opts)
     |> restrict_replies(opts)
     |> restrict_reblogs(opts)
+    |> restrict_pinned(opts)
   end
 
   def fetch_activities(recipients, opts \\ %{}) do
index bb3c38f006f0715b8801cc79640c5b3a22c1a954..7ec6aa0ea8eff3089c7535c78c9e2d73b2909668 100644 (file)
@@ -164,4 +164,48 @@ defmodule Pleroma.Web.CommonAPI do
       object: Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
     })
   end
+
+  def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
+    with %Activity{
+           actor: ^user_ap_id,
+           data: %{
+             "type" => "Create",
+             "object" => %{
+               "to" => object_to,
+               "type" => "Note"
+             }
+           }
+         } = activity <- get_by_id_or_ap_id(id_or_ap_id),
+         true <- Enum.member?(object_to, "https://www.w3.org/ns/activitystreams#Public"),
+         %{valid?: true} = info_changeset <-
+           Pleroma.User.Info.add_pinnned_activity(user.info, activity),
+         changeset <-
+           Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
+         {:ok, _user} <- User.update_and_set_cache(changeset) do
+      {:ok, activity}
+    else
+      %{errors: [pinned_activities: {err, _}]} ->
+        {:error, err}
+
+      _ ->
+        {:error, "Could not pin"}
+    end
+  end
+
+  def unpin(id_or_ap_id, user) do
+    with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
+         %{valid?: true} = info_changeset <-
+           Pleroma.User.Info.remove_pinnned_activity(user.info, activity),
+         changeset <-
+           Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
+         {:ok, _user} <- User.update_and_set_cache(changeset) do
+      {:ok, activity}
+    else
+      %{errors: [pinned_activities: {err, _}]} ->
+        {:error, err}
+
+      _ ->
+        {:error, "Could not unpin"}
+    end
+  end
 end
index f739e8f7d01459607b0c82dde1956899048aa84c..e00a3fb8721849d8eaa302aed7ee23b3ff7f50b8 100644 (file)
@@ -256,13 +256,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
 
   def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do
     with %User{} = user <- Repo.get(User, params["id"]) do
-      # Since Pleroma has no "pinned" posts feature, we'll just set an empty list here
-      activities =
-        if params["pinned"] == "true" do
-          []
-        else
-          ActivityPub.fetch_user_activities(user, reading_user, params)
-        end
+      activities = ActivityPub.fetch_user_activities(user, reading_user, params)
 
       conn
       |> add_link_headers(:user_statuses, activities, params["id"])
@@ -409,6 +403,27 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     end
   end
 
+  def pin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
+    with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do
+      conn
+      |> put_view(StatusView)
+      |> try_render("status.json", %{activity: activity, for: user, as: :activity})
+    else
+      {:error, reason} ->
+        conn
+        |> put_resp_content_type("application/json")
+        |> send_resp(:bad_request, Jason.encode!(%{"error" => reason}))
+    end
+  end
+
+  def unpin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
+    with {:ok, activity} <- CommonAPI.unpin(ap_id_or_id, user) do
+      conn
+      |> put_view(StatusView)
+      |> try_render("status.json", %{activity: activity, for: user, as: :activity})
+    end
+  end
+
   def notifications(%{assigns: %{user: user}} = conn, params) do
     notifications = Notification.for_user(user, params)
 
index 8e8fa8121c475d79f809bc41af3a1aa5f78b3b48..db543ffe57a88e40479ee74ba981d694526a1c85 100644 (file)
@@ -76,6 +76,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
       reblogged: false,
       favourited: false,
       muted: false,
+      pinned: pinned?(activity, user),
       sensitive: false,
       spoiler_text: "",
       visibility: "public",
@@ -142,6 +143,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
       reblogged: present?(repeated),
       favourited: present?(favorited),
       muted: false,
+      pinned: pinned?(activity, user),
       sensitive: sensitive,
       spoiler_text: object["summary"] || "",
       visibility: get_visibility(object),
@@ -295,4 +297,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
   defp present?(nil), do: false
   defp present?(false), do: false
   defp present?(_), do: true
+
+  defp pinned?(%Activity{id: id}, %User{info: %{pinned_activities: pinned_activities}}),
+    do: id in pinned_activities
 end
index 8df45bf4da2344053a835375019b580529ed05e9..a5f4d812681ce08dba458301b0b6dbb1e8669222 100644 (file)
@@ -188,6 +188,8 @@ defmodule Pleroma.Web.Router do
     post("/statuses/:id/unreblog", MastodonAPIController, :unreblog_status)
     post("/statuses/:id/favourite", MastodonAPIController, :fav_status)
     post("/statuses/:id/unfavourite", MastodonAPIController, :unfav_status)
+    post("/statuses/:id/pin", MastodonAPIController, :pin_status)
+    post("/statuses/:id/unpin", MastodonAPIController, :unpin_status)
 
     post("/notifications/clear", MastodonAPIController, :clear_notifications)
     post("/notifications/dismiss", MastodonAPIController, :dismiss_notification)
@@ -353,6 +355,9 @@ defmodule Pleroma.Web.Router do
     post("/statuses/unretweet/:id", TwitterAPI.Controller, :unretweet)
     post("/statuses/destroy/:id", TwitterAPI.Controller, :delete_post)
 
+    post("/statuses/pin/:id", TwitterAPI.Controller, :pin)
+    post("/statuses/unpin/:id", TwitterAPI.Controller, :unpin)
+
     get("/pleroma/friend_requests", TwitterAPI.Controller, :friend_requests)
     post("/pleroma/friendships/approve", TwitterAPI.Controller, :approve_friend_request)
     post("/pleroma/friendships/deny", TwitterAPI.Controller, :deny_friend_request)
index 47154829dfede3a57e6039ba68c07a111d1ffc2f..4f8f228ab87c68eee620f66a4ca7fbd0a76aa918 100644 (file)
@@ -153,6 +153,7 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
     announcement_count = object["announcement_count"] || 0
     favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || [])
     repeated = opts[:for] && opts[:for].ap_id in (object["announcements"] || [])
+    pinned = activity.id in user.info.pinned_activities
 
     mentions = opts[:mentioned] || []
 
@@ -204,6 +205,7 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
       "repeat_num" => announcement_count,
       "favorited" => to_boolean(favorited),
       "repeated" => to_boolean(repeated),
+      "pinned" => pinned,
       "external_url" => object["external_url"] || object["id"],
       "tags" => tags,
       "activity_type" => "post",
index ecf81d492799aaa1fa179ef1ccbbbef9ce75779f..7a63724f1c64d9635cdf909264a08e6c4a749aa2 100644 (file)
@@ -82,6 +82,14 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
     end
   end
 
+  def pin(%User{} = user, ap_id_or_id) do
+    CommonAPI.pin(ap_id_or_id, user)
+  end
+
+  def unpin(%User{} = user, ap_id_or_id) do
+    CommonAPI.unpin(ap_id_or_id, user)
+  end
+
   def fav(%User{} = user, ap_id_or_id) do
     with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user),
          %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
index 1e04b8c4bea52c72b6300d54079448cc4d5c62df..d5b9d2fb88ca7a3740be700df5ed02b84a12b197 100644 (file)
@@ -375,6 +375,30 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
     end
   end
 
+  def pin(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+    with {_, {:ok, id}} <- {:param_cast, Ecto.Type.cast(:integer, id)},
+         {:ok, activity} <- TwitterAPI.pin(user, id) do
+      conn
+      |> put_view(ActivityView)
+      |> render("activity.json", %{activity: activity, for: user})
+    else
+      {:error, message} -> bad_request_reply(conn, message)
+      err -> err
+    end
+  end
+
+  def unpin(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+    with {_, {:ok, id}} <- {:param_cast, Ecto.Type.cast(:integer, id)},
+         {:ok, activity} <- TwitterAPI.unpin(user, id) do
+      conn
+      |> put_view(ActivityView)
+      |> render("activity.json", %{activity: activity, for: user})
+    else
+      {:error, message} -> bad_request_reply(conn, message)
+      err -> err
+    end
+  end
+
   def register(conn, params) do
     with {:ok, user} <- TwitterAPI.register_user(params) do
       conn
index 5f4b748429195905c087d0d292ab3dc8dc7e5d26..108e7bfc5208d3d19def3aefc4c525c1a3669b6b 100644 (file)
@@ -243,6 +243,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
     announcement_count = object["announcement_count"] || 0
     favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || [])
     repeated = opts[:for] && opts[:for].ap_id in (object["announcements"] || [])
+    pinned = activity.id in user.info.pinned_activities
 
     attentions =
       activity.recipients
@@ -302,6 +303,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
       "repeat_num" => announcement_count,
       "favorited" => !!favorited,
       "repeated" => !!repeated,
+      "pinned" => pinned,
       "external_url" => object["external_url"] || object["id"],
       "tags" => tags,
       "activity_type" => "post",
index 47aa5a56f3b3ff9cc82c18fe66b4ffd755b00120..eafb96f3a303cfd798577e251c3e4754e3d9c878 100644 (file)
@@ -637,6 +637,28 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     assert object
   end
 
+  test "returned pinned statuses" do
+    Pleroma.Config.put([:instance, :max_pinned_statuses], 3)
+    user = insert(:user)
+
+    {:ok, activity_one} = CommonAPI.post(user, %{"status" => "HI!!!"})
+    {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
+    {:ok, activity_three} = CommonAPI.post(user, %{"status" => "HI!!!"})
+
+    CommonAPI.pin(activity_one.id, user)
+    user = refresh_record(user)
+
+    CommonAPI.pin(activity_two.id, user)
+    user = refresh_record(user)
+
+    CommonAPI.pin(activity_three.id, user)
+    user = refresh_record(user)
+
+    activities = ActivityPub.fetch_user_activities(user, nil, %{"pinned" => "true"})
+
+    assert 3 = length(activities)
+  end
+
   def data_uri do
     File.read!("test/fixtures/avatar_data_uri")
   end
index c3674711aee7e7cfd5daa65bd7c53856790ca089..eb69ea4b2682ec88d48ca87b8d490d4e0cf6e8cf 100644 (file)
@@ -1,5 +1,5 @@
 # Pleroma: A lightweight social networking server
-# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.CommonAPI.Test do
@@ -96,4 +96,42 @@ defmodule Pleroma.Web.CommonAPI.Test do
       {:error, _} = CommonAPI.favorite(activity.id, user)
     end
   end
+
+  describe "pinned statuses" do
+    setup do
+      Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
+
+      user = insert(:user)
+      {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
+
+      [user: user, activity: activity]
+    end
+
+    test "pin status", %{user: user, activity: activity} do
+      assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
+    end
+
+    test "only self-authored can be pinned", %{activity: activity} do
+      user = insert(:user)
+
+      assert {:error, "Could not pin"} = CommonAPI.pin(activity.id, user)
+    end
+
+    test "max pinned statuses", %{user: user, activity: activity_one} do
+      {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
+
+      assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
+
+      user = refresh_record(user)
+
+      assert {:error, "You have already pinned the maximum number of statuses"} =
+               CommonAPI.pin(activity_two.id, user)
+    end
+
+    test "unpin status", %{user: user, activity: activity} do
+      {:ok, activity} = CommonAPI.pin(activity.id, user)
+
+      assert {:ok, ^activity} = CommonAPI.unpin(activity.id, user)
+    end
+  end
 end
index ce87010c8edcc112cab849db5ecb17e7eb904a96..b448d13f5323ef069d94b15b68c92a7fafcfe491 100644 (file)
@@ -1,5 +1,5 @@
 # Pleroma: A lightweight social networking server
-# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
@@ -1471,4 +1471,84 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     user = User.get_cached_by_ap_id(user.ap_id)
     assert user.info.settings == %{"programming" => "socks"}
   end
+
+  describe "pinned statuses" do
+    setup do
+      Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
+
+      user = insert(:user)
+      {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
+
+      [user: user, activity: activity]
+    end
+
+    test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do
+      {:ok, _} = CommonAPI.pin(activity.id, user)
+
+      result =
+        conn
+        |> assign(:user, user)
+        |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
+        |> json_response(200)
+
+      id_str = to_string(activity.id)
+
+      assert [%{"id" => ^id_str, "pinned" => true}] = result
+    end
+
+    test "pin status", %{conn: conn, user: user, activity: activity} do
+      id_str = to_string(activity.id)
+
+      assert %{"id" => ^id_str, "pinned" => true} =
+               conn
+               |> assign(:user, user)
+               |> post("/api/v1/statuses/#{activity.id}/pin")
+               |> json_response(200)
+
+      assert [%{"id" => ^id_str, "pinned" => true}] =
+               conn
+               |> assign(:user, user)
+               |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
+               |> json_response(200)
+    end
+
+    test "unpin status", %{conn: conn, user: user, activity: activity} do
+      {:ok, _} = CommonAPI.pin(activity.id, user)
+
+      id_str = to_string(activity.id)
+      user = refresh_record(user)
+
+      assert %{"id" => ^id_str, "pinned" => false} =
+               conn
+               |> assign(:user, user)
+               |> post("/api/v1/statuses/#{activity.id}/unpin")
+               |> json_response(200)
+
+      assert [] =
+               conn
+               |> assign(:user, user)
+               |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
+               |> json_response(200)
+    end
+
+    test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
+      {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
+
+      id_str_one = to_string(activity_one.id)
+
+      assert %{"id" => ^id_str_one, "pinned" => true} =
+               conn
+               |> assign(:user, user)
+               |> post("/api/v1/statuses/#{id_str_one}/pin")
+               |> json_response(200)
+
+      user = refresh_record(user)
+
+      assert %{"error" => "You have already pinned the maximum number of statuses"} =
+               conn
+               |> assign(:user, user)
+               |> post("/api/v1/statuses/#{activity_two.id}/pin")
+               |> json_response(400)
+    end
+  end
 end
index b953ccd76989bd96e1a99220a2020444ad755d4e..1076b500252455068be3f2036d7ae5ffccc7743c 100644 (file)
@@ -63,6 +63,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
       reblogged: false,
       favourited: false,
       muted: false,
+      pinned: false,
       sensitive: false,
       spoiler_text: note.data["object"]["summary"],
       visibility: "public",
index d71aaacfe8a40fbdd111778039aeb9ffd68627c0..ef0294140d23d8d1c3eb4bf09aee3c3b67fa8418 100644 (file)
@@ -1,5 +1,5 @@
 # Pleroma: A lightweight social networking server
-# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenterTest do
@@ -157,6 +157,7 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenterTest do
       "repeat_num" => 3,
       "favorited" => false,
       "repeated" => false,
+      "pinned" => false,
       "external_url" => "some url",
       "tags" => ["nsfw", "content", "mentioning"],
       "activity_type" => "post",
index c41f615ac261ed6dcb22e9ac61f1451d15875e50..7d4c92c66ab1fd6a62512bf05a262b6fbe2d1f45 100644 (file)
@@ -1,5 +1,5 @@
 # Pleroma: A lightweight social networking server
-# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.TwitterAPI.ControllerTest do
@@ -1694,4 +1694,79 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
       assert object.data["name"] == description
     end
   end
+
+  describe "POST /api/statuses/user_timeline.json?user_id=:user_id&pinned=true" do
+    test "it returns a list of pinned statuses", %{conn: conn} do
+      Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
+
+      user = insert(:user, %{name: "egor"})
+      {:ok, %{id: activity_id}} = CommonAPI.post(user, %{"status" => "HI!!!"})
+      {:ok, _} = CommonAPI.pin(activity_id, user)
+
+      resp =
+        conn
+        |> get("/api/statuses/user_timeline.json", %{user_id: user.id, pinned: true})
+        |> json_response(200)
+
+      assert length(resp) == 1
+      assert [%{"id" => ^activity_id, "pinned" => true}] = resp
+    end
+  end
+
+  describe "POST /api/statuses/pin/:id" do
+    setup do
+      Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
+      [user: insert(:user)]
+    end
+
+    test "without valid credentials", %{conn: conn} do
+      note_activity = insert(:note_activity)
+      conn = post(conn, "/api/statuses/pin/#{note_activity.id}.json")
+      assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
+    end
+
+    test "with credentials", %{conn: conn, user: user} do
+      {:ok, activity} = CommonAPI.post(user, %{"status" => "test!"})
+
+      request_path = "/api/statuses/pin/#{activity.id}.json"
+
+      response =
+        conn
+        |> with_credentials(user.nickname, "test")
+        |> post(request_path)
+
+      user = refresh_record(user)
+
+      assert json_response(response, 200) == ActivityRepresenter.to_map(activity, %{user: user})
+    end
+  end
+
+  describe "POST /api/statuses/unpin/:id" do
+    setup do
+      Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
+      [user: insert(:user)]
+    end
+
+    test "without valid credentials", %{conn: conn} do
+      note_activity = insert(:note_activity)
+      conn = post(conn, "/api/statuses/unpin/#{note_activity.id}.json")
+      assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
+    end
+
+    test "with credentials", %{conn: conn, user: user} do
+      {:ok, activity} = CommonAPI.post(user, %{"status" => "test!"})
+      {:ok, activity} = CommonAPI.pin(activity.id, user)
+
+      request_path = "/api/statuses/unpin/#{activity.id}.json"
+
+      response =
+        conn
+        |> with_credentials(user.nickname, "test")
+        |> post(request_path)
+
+      user = refresh_record(user)
+
+      assert json_response(response, 200) == ActivityRepresenter.to_map(activity, %{user: user})
+    end
+  end
 end
index 5294204c6fda97e03dfc3d4a294ab2461b6d99ad..8b5a16add9b12d30f0cf0be0c067620e01ee469e 100644 (file)
@@ -1,5 +1,5 @@
 # Pleroma: A lightweight social networking server
-# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
@@ -136,6 +136,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
       "possibly_sensitive" => false,
       "repeat_num" => 0,
       "repeated" => false,
+      "pinned" => false,
       "statusnet_conversation_id" => convo_id,
       "summary" => "",
       "summary_html" => "",