Merge branch 'develop' into feature/reports-groups-and-multiple-state-update
authorMaxim Filippov <colixer@gmail.com>
Mon, 4 Nov 2019 23:12:48 +0000 (02:12 +0300)
committerMaxim Filippov <colixer@gmail.com>
Mon, 4 Nov 2019 23:12:48 +0000 (02:12 +0300)
1  2 
CHANGELOG.md
docs/API/admin_api.md
lib/pleroma/web/activity_pub/utils.ex
lib/pleroma/web/admin_api/admin_api_controller.ex
lib/pleroma/web/common_api/common_api.ex
lib/pleroma/web/router.ex
test/web/admin_api/admin_api_controller_test.exs
test/web/common_api/common_api_test.exs

diff --cc CHANGELOG.md
index c51807c8069662b548b1453ba6201956e6c1e196,77edf7bf0d2897e780013109b3e7e061e853131b..64a28218d20fce42cb2595e2a06f9e029194d441
@@@ -14,10 -46,13 +46,14 @@@ The format is based on [Keep a Changelo
  - Mastodon API: Add `upload_limit`, `avatar_upload_limit`, `background_upload_limit`, and `banner_upload_limit` to `/api/v1/instance`
  - Mastodon API: Add `pleroma.unread_conversation_count` to the Account entity
  - OAuth: support for hierarchical permissions / [Mastodon 2.4.3 OAuth permissions](https://docs.joinmastodon.org/api/permissions/)
- - Authentication: Added rate limit for password-authorized actions / login existence checks
  - Metadata Link: Atom syndication Feed
- - Mix task to re-count statuses for all users (`mix pleroma.count_statuses`)
+ - Mastodon API: Add `exclude_visibilities` parameter to the timeline and notification endpoints
+ - Admin API: `/users/:nickname/toggle_activation` endpoint is now deprecated in favor of: `/users/activate`, `/users/deactivate`, both accept `nicknames` array
+ - Admin API: `POST/DELETE /api/pleroma/admin/users/:nickname/permission_group/:permission_group` are deprecated in favor of: `POST/DELETE /api/pleroma/admin/users/permission_group/:permission_group` (both accept `nicknames` array), `DELETE /api/pleroma/admin/users` (`nickname` query param or `nickname` sent in JSON body) is deprecated in favor of: `DELETE /api/pleroma/admin/users` (`nicknames` query array param or `nicknames` sent in JSON body).
+ - Admin API: Add `GET /api/pleroma/admin/relay` endpoint - lists all followed relays
+ - Pleroma API: `POST /api/v1/pleroma/conversations/read` to mark all conversations as read
+ - Mastodon API: Add `/api/v1/markers` for managing timeline read markers
 +- Admin API: Add ability to fetch reports, grouped by status `GET /api/pleroma/admin/grouped_reports`
  
  ### Changed
  - **Breaking:** Elixir >=1.8 is now required (was >= 1.7)
index e8232225c6f969428f35567eb267c4523f17a447,e64ae6429496266b4dd4043a22f3922bc563f80c..9499d0140b3a0612001d861c73c21c682eb2cebd
@@@ -156,10 -170,20 +165,22 @@@ Note: Available `:permission_group` is 
  - Params: none
  - Response:
    - On failure: `{"error": "…"}`
-   - On success: JSON of the `user.info`
+   - On success: JSON of the user
+ ## `POST /api/pleroma/admin/users/permission_group/:permission_group`
+ ### Add users to permission group
+ - Params:
+   - `nicknames`: nicknames array
+ - Response:
+   - On failure: `{"error": "…"}`
+   - On success: JSON of the user
+ ## DEPRECATED `DELETE /api/pleroma/admin/users/:nickname/permission_group/:permission_group`
  
 +## `DELETE /api/pleroma/admin/users/:nickname/permission_group/:permission_group`
 +
  ### Remove user from permission group
  
  - Params: none
  - Response:
    - On success: URL of the unfollowed relay
  
 -## `/api/pleroma/admin/users/invite_token`
+ ## `GET /api/pleroma/admin/relay`
+ ### List Relays
+ - Params: none
+ - Response:
+   - On success: JSON array of relays
 +## `POST /api/pleroma/admin/users/invite_token`
  
  ### Create an account registration invite token
  
index f2beb0809710edc28f5c5fd7b4e224bd6e1bb738,d812fd734292cf9d3b481cbfe2dc520517653a3f..57349e30463cb7d8a190d426bd89acea91f99b5c
@@@ -12,8 -11,8 +12,9 @@@ defmodule Pleroma.Web.ActivityPub.Util
    alias Pleroma.Repo
    alias Pleroma.User
    alias Pleroma.Web
 +  alias Pleroma.Web.ActivityPub.ActivityPub
    alias Pleroma.Web.ActivityPub.Visibility
+   alias Pleroma.Web.AdminAPI.AccountView
    alias Pleroma.Web.Endpoint
    alias Pleroma.Web.Router.Helpers
  
    end
  
    #### Report-related helpers
 +  def get_reports(params, page, page_size) do
 +    params =
 +      params
 +      |> Map.put("type", "Flag")
 +      |> Map.put("skip_preload", true)
 +      |> Map.put("total", true)
 +      |> Map.put("limit", page_size)
 +      |> Map.put("offset", (page - 1) * page_size)
 +
 +    ActivityPub.fetch_activities([], params, :offset)
 +  end
 +
 +  @spec get_reports_grouped_by_status() :: %{
 +          required(:groups) => [
 +            %{
 +              required(:date) => String.t(),
 +              required(:account) => %User{},
 +              required(:status) => %Activity{},
 +              required(:actors) => [%User{}],
 +              required(:reports) => [%Activity{}]
 +            }
 +          ],
 +          required(:total) => integer
 +        }
 +  def get_reports_grouped_by_status do
 +    paginated_activities = get_reported_status_ids()
 +
 +    groups =
 +      paginated_activities
 +      |> Enum.map(fn entry ->
 +        status =
 +          Activity
 +          |> Queries.by_ap_id(entry[:activity_id])
 +          |> Activity.with_preloaded_object(:left)
 +          |> Activity.with_preloaded_user_actor()
 +          |> Repo.one()
 +
 +        reports = get_reports_by_status_id(status.data["id"])
 +
 +        max_date =
 +          Enum.max_by(reports, &Pleroma.Web.CommonAPI.Utils.to_masto_date(&1.data["published"])).data[
 +            "published"
 +          ]
 +
 +        actors = Enum.map(reports, & &1.user_actor)
 +
 +        %{
 +          date: max_date,
 +          account: status.user_actor,
 +          status: status,
 +          actors: actors,
 +          reports: reports
 +        }
 +      end)
 +
 +    %{
 +      groups: groups
 +    }
 +  end
 +
 +  def get_reports_by_status_id(status_id) do
 +    from(a in Activity,
 +      where: fragment("(?)->>'type' = 'Flag'", a.data),
 +      where: fragment("(?)->'object' \\? (?)", a.data, ^status_id)
 +    )
 +    |> Activity.with_preloaded_user_actor()
 +    |> Repo.all()
 +  end
 +
 +  @spec get_reported_status_ids() :: %{
 +          required(:items) => [%Activity{}],
 +          required(:total) => integer
 +        }
 +  def get_reported_status_ids do
 +    from(a in Activity,
 +      where: fragment("(?)->>'type' = 'Flag'", a.data),
 +      select: %{
 +        date: fragment("max(?->>'published') date", a.data),
 +        activity_id:
 +          fragment("jsonb_array_elements_text((? #- '{object,0}')->'object') activity_id", a.data)
 +      },
 +      group_by: fragment("activity_id"),
 +      order_by: fragment("date DESC")
 +    )
 +    |> Repo.all()
 +  end
  
+   def update_report_state(%Activity{} = activity, state)
+       when state in @strip_status_report_states do
+     {:ok, stripped_activity} = strip_report_status_data(activity)
+     new_data =
+       activity.data
+       |> Map.put("state", state)
+       |> Map.put("object", stripped_activity.data["object"])
+     activity
+     |> Changeset.change(data: new_data)
+     |> Repo.update()
+   end
    def update_report_state(%Activity{} = activity, state) when state in @supported_report_states do
      new_data = Map.put(activity.data, "state", state)
  
      |> Repo.update()
    end
  
 +  def update_report_state(activity_ids, state) when state in @supported_report_states do
 +    activities_num = length(activity_ids)
 +
 +    from(a in Activity, where: a.id in ^activity_ids)
 +    |> update(set: [data: fragment("jsonb_set(data, '{state}', ?)", ^state)])
 +    |> Repo.update_all([])
 +    |> case do
 +      {^activities_num, _} -> :ok
 +      _ -> {:error, activity_ids}
 +    end
 +  end
 +
    def update_report_state(_, _), do: {:error, "Unsupported state"}
  
+   def strip_report_status_data(activity) do
+     [actor | reported_activities] = activity.data["object"]
+     stripped_activities = Enum.map(reported_activities, & &1["id"])
+     new_data = put_in(activity.data, ["object"], [actor | stripped_activities])
+     {:ok, %{activity | data: new_data}}
+   end
    def update_activity_visibility(activity, visibility) when visibility in @valid_visibilities do
      [to, cc, recipients] =
        activity
Simple merge
index daa0631db518ce3e80e2976675c23459b89fe6dc,045c87e952bef70158c0ddd5aca0c68607042f80..35367bed3469cfef76e0dee9be5019524de2a6ee
@@@ -1224,9 -1312,9 +1312,9 @@@ defmodule Pleroma.Web.AdminAPI.AdminAPI
      end
    end
  
 -  describe "PUT /api/pleroma/admin/reports/:id" do
 +  describe "PATCH /api/pleroma/admin/reports" do
      setup %{conn: conn} do
-       admin = insert(:user, info: %{is_admin: true})
+       admin = insert(:user, is_admin: true)
        [reporter, target_user] = insert_pair(:user)
        activity = insert(:note_activity, user: target_user)
  
      end
    end
  
 -  #
 +  describe "GET /api/pleroma/admin/grouped_reports" do
 +    setup %{conn: conn} do
 +      admin = insert(:user, info: %{is_admin: true})
 +      [reporter, target_user] = insert_pair(:user)
 +
 +      date1 = (DateTime.to_unix(DateTime.utc_now()) + 1000) |> DateTime.from_unix!()
 +      date2 = (DateTime.to_unix(DateTime.utc_now()) + 2000) |> DateTime.from_unix!()
 +      date3 = (DateTime.to_unix(DateTime.utc_now()) + 3000) |> DateTime.from_unix!()
 +
 +      first_status =
 +        insert(:note_activity, user: target_user, data_attrs: %{"published" => date1})
 +
 +      second_status =
 +        insert(:note_activity, user: target_user, data_attrs: %{"published" => date2})
 +
 +      third_status =
 +        insert(:note_activity, user: target_user, data_attrs: %{"published" => date3})
 +
 +      %{
 +        conn: assign(conn, :user, admin),
 +        reporter: reporter,
 +        target_user: target_user,
 +        first_status: first_status,
 +        second_status: second_status,
 +        third_status: third_status
 +      }
 +    end
 +
 +    test "returns reports grouped by status", %{
 +      conn: conn,
 +      reporter: reporter,
 +      target_user: target_user,
 +      first_status: first_status,
 +      second_status: second_status,
 +      third_status: third_status
 +    } do
 +      {:ok, %{id: _}} =
 +        CommonAPI.report(reporter, %{
 +          "account_id" => target_user.id,
 +          "status_ids" => [first_status.id, second_status.id, third_status.id]
 +        })
 +
 +      {:ok, %{id: _}} =
 +        CommonAPI.report(reporter, %{
 +          "account_id" => target_user.id,
 +          "status_ids" => [first_status.id, second_status.id]
 +        })
 +
 +      {:ok, %{id: _}} =
 +        CommonAPI.report(reporter, %{
 +          "account_id" => target_user.id,
 +          "status_ids" => [first_status.id]
 +        })
 +
 +      response =
 +        conn
 +        |> get("/api/pleroma/admin/grouped_reports")
 +        |> json_response(:ok)
 +
 +      assert length(response["reports"]) == 3
 +      [third_group, second_group, first_group] = response["reports"]
 +
 +      assert length(third_group["reports"]) == 3
 +      assert length(second_group["reports"]) == 2
 +      assert length(first_group["reports"]) == 1
 +    end
 +  end
 +
    describe "POST /api/pleroma/admin/reports/:id/respond" do
      setup %{conn: conn} do
-       admin = insert(:user, info: %{is_admin: true})
+       admin = insert(:user, is_admin: true)
  
        %{conn: assign(conn, :user, admin), admin: admin}
      end