diff --git a/CHANGELOG.md b/CHANGELOG.md index 56b6371d5..37b8b0be7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - **Breaking:** Admin API: Return link alongside with token on password reset - **Breaking:** Admin API: `PUT /api/pleroma/admin/reports/:id` is now `PATCH /api/pleroma/admin/reports`, see admin_api.md for details - **Breaking:** `/api/pleroma/admin/users/invite_token` now uses `POST`, changed accepted params and returns full invite in json instead of only token string. +- **Breaking** replying to reports is now "report notes", enpoint changed from `POST /api/pleroma/admin/reports/:id/respond` to `POST /api/pleroma/admin/reports/:id/notes` - Admin API: Return `total` when querying for reports - Mastodon API: Return `pleroma.direct_conversation_id` when creating a direct message (`POST /api/v1/statuses`) - Admin API: Return link alongside with token on password reset @@ -91,6 +92,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - MRF: `Delete` activities being exempt from MRF policies - OTP releases: Not being able to configure OAuth expired token cleanup interval - OTP releases: Not being able to configure HTML sanitization policy +- Favorites timeline now ordered by favorite date instead of post date
API Changes diff --git a/config/test.exs b/config/test.exs index 9b737d4d7..d52ced612 100644 --- a/config/test.exs +++ b/config/test.exs @@ -68,7 +68,9 @@ queues: false, prune: :disabled -config :pleroma, Pleroma.Scheduler, jobs: [] +config :pleroma, Pleroma.Scheduler, + jobs: [], + global: false config :pleroma, Pleroma.ScheduledActivity, daily_user_limit: 2, diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md index b19793150..d98a78af0 100644 --- a/docs/API/admin_api.md +++ b/docs/API/admin_api.md @@ -614,78 +614,29 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret - On success: `204`, empty response -## `POST /api/pleroma/admin/reports/:id/respond` +## `POST /api/pleroma/admin/reports/:id/notes` -### Respond to a report +### Create report note - Params: - - `id` - - `status`: required, the message + - `id`: required, report id + - `content`: required, the message - Response: - On failure: - 400 Bad Request `"Invalid parameters"` when `status` is missing - - 403 Forbidden `{"error": "error_msg"}` - - 404 Not Found `"Not found"` - - On success: JSON, created Mastodon Status entity + - On success: `204`, empty response -```json -{ - "account": { ... }, - "application": { - "name": "Web", - "website": null - }, - "bookmarked": false, - "card": null, - "content": "Your claim is going to be closed", - "created_at": "2019-05-11T17:13:03.000Z", - "emojis": [], - "favourited": false, - "favourites_count": 0, - "id": "9ihuiSL1405I65TmEq", - "in_reply_to_account_id": null, - "in_reply_to_id": null, - "language": null, - "media_attachments": [], - "mentions": [ - { - "acct": "user", - "id": "9i6dAJqSGSKMzLG2Lo", - "url": "https://pleroma.example.org/users/user", - "username": "user" - }, - { - "acct": "admin", - "id": "9hEkA5JsvAdlSrocam", - "url": "https://pleroma.example.org/users/admin", - "username": "admin" - } - ], - "muted": false, - "pinned": false, - "pleroma": { - "content": { - "text/plain": "Your claim is going to be closed" - }, - "conversation_id": 35, - "in_reply_to_account_acct": null, - "local": true, - "spoiler_text": { - "text/plain": "" - } - }, - "reblog": null, - "reblogged": false, - "reblogs_count": 0, - "replies_count": 0, - "sensitive": false, - "spoiler_text": "", - "tags": [], - "uri": "https://pleroma.example.org/objects/cab0836d-9814-46cd-a0ea-529da9db5fcb", - "url": "https://pleroma.example.org/notice/9ihuiSL1405I65TmEq", - "visibility": "direct" -} -``` +## `POST /api/pleroma/admin/reports/:report_id/notes/:id` + +### Delete report note + +- Params: + - `report_id`: required, report id + - `id`: required, note id +- Response: + - On failure: + - 400 Bad Request `"Invalid parameters"` when `status` is missing + - On success: `204`, empty response ## `PUT /api/pleroma/admin/statuses/:id` diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 480b261cf..510d3273c 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -12,6 +12,7 @@ defmodule Pleroma.Activity do alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Repo + alias Pleroma.ReportNote alias Pleroma.ThreadMute alias Pleroma.User @@ -48,6 +49,8 @@ defmodule Pleroma.Activity do has_one(:user_actor, User, on_delete: :nothing, foreign_key: :id) # This is a fake relation, do not use outside of with_preloaded_bookmark/get_bookmark has_one(:bookmark, Bookmark) + # This is a fake relation, do not use outside of with_preloaded_report_notes + has_many(:report_notes, ReportNote) has_many(:notifications, Notification, on_delete: :delete_all) # Attention: this is a fake relation, don't try to preload it blindly and expect it to work! @@ -114,6 +117,16 @@ def with_preloaded_bookmark(query, %User{} = user) do def with_preloaded_bookmark(query, _), do: query + def with_preloaded_report_notes(query) do + from([a] in query, + left_join: r in ReportNote, + on: a.id == r.activity_id, + preload: [report_notes: r] + ) + end + + def with_preloaded_report_notes(query, _), do: query + def with_set_thread_muted_field(query, %User{} = user) do from([a] in query, left_join: tm in ThreadMute, diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex index 706f089dc..c81477f48 100644 --- a/lib/pleroma/moderation_log.ex +++ b/lib/pleroma/moderation_log.ex @@ -128,17 +128,35 @@ def insert_log(%{ {:ok, ModerationLog} | {:error, any} def insert_log(%{ actor: %User{} = actor, - action: "report_response", + action: "report_note", subject: %Activity{} = subject, text: text }) do %ModerationLog{ data: %{ "actor" => user_to_map(actor), - "action" => "report_response", + "action" => "report_note", "subject" => report_to_map(subject), - "text" => text, - "message" => "" + "text" => text + } + } + |> insert_log_entry_with_message() + end + + @spec insert_log(%{actor: User, subject: Activity, action: String.t(), text: String.t()}) :: + {:ok, ModerationLog} | {:error, any} + def insert_log(%{ + actor: %User{} = actor, + action: "report_note_delete", + subject: %Activity{} = subject, + text: text + }) do + %ModerationLog{ + data: %{ + "actor" => user_to_map(actor), + "action" => "report_note_delete", + "subject" => report_to_map(subject), + "text" => text } } |> insert_log_entry_with_message() @@ -556,12 +574,24 @@ def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{ data: %{ "actor" => %{"nickname" => actor_nickname}, - "action" => "report_response", + "action" => "report_note", "subject" => %{"id" => subject_id, "type" => "report"}, "text" => text } }) do - "@#{actor_nickname} responded with '#{text}' to report ##{subject_id}" + "@#{actor_nickname} added note '#{text}' to report ##{subject_id}" + end + + @spec get_log_entry_message(ModerationLog) :: String.t() + def get_log_entry_message(%ModerationLog{ + data: %{ + "actor" => %{"nickname" => actor_nickname}, + "action" => "report_note_delete", + "subject" => %{"id" => subject_id, "type" => "report"}, + "text" => text + } + }) do + "@#{actor_nickname} deleted note '#{text}' from report ##{subject_id}" end @spec get_log_entry_message(ModerationLog) :: String.t() diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index ff0e59241..eb37b95a6 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -23,6 +23,23 @@ defmodule Pleroma.Object do timestamps() end + def with_joined_activity(query, activity_type \\ "Create", join_type \\ :inner) do + object_position = Map.get(query.aliases, :object, 0) + + join(query, join_type, [{object, object_position}], a in Activity, + on: + fragment( + "COALESCE(?->'object'->>'id', ?->>'object') = (? ->> 'id') AND (?->>'type' = ?) ", + a.data, + a.data, + object.data, + a.data, + ^activity_type + ), + as: :object_activity + ) + end + def create(data) do Object.change(%Object{}, %{data: data}) |> Repo.insert() diff --git a/lib/pleroma/pagination.ex b/lib/pleroma/pagination.ex index 9d279fba7..4535ca7c5 100644 --- a/lib/pleroma/pagination.ex +++ b/lib/pleroma/pagination.ex @@ -13,60 +13,66 @@ defmodule Pleroma.Pagination do alias Pleroma.Repo @default_limit 20 + @page_keys ["max_id", "min_id", "limit", "since_id", "order"] - def fetch_paginated(query, params, type \\ :keyset) + def page_keys, do: @page_keys - def fetch_paginated(query, %{"total" => true} = params, :keyset) do + def fetch_paginated(query, params, type \\ :keyset, table_binding \\ nil) + + def fetch_paginated(query, %{"total" => true} = params, :keyset, table_binding) do total = Repo.aggregate(query, :count, :id) %{ total: total, - items: fetch_paginated(query, Map.drop(params, ["total"]), :keyset) + items: fetch_paginated(query, Map.drop(params, ["total"]), :keyset, table_binding) } end - def fetch_paginated(query, params, :keyset) do + def fetch_paginated(query, params, :keyset, table_binding) do options = cast_params(params) query - |> paginate(options, :keyset) + |> paginate(options, :keyset, table_binding) |> Repo.all() |> enforce_order(options) end - def fetch_paginated(query, %{"total" => true} = params, :offset) do - total = Repo.aggregate(query, :count, :id) + def fetch_paginated(query, %{"total" => true} = params, :offset, table_binding) do + total = + query + |> Ecto.Query.exclude(:left_join) + |> Repo.aggregate(:count, :id) %{ total: total, - items: fetch_paginated(query, Map.drop(params, ["total"]), :offset) + items: fetch_paginated(query, Map.drop(params, ["total"]), :offset, table_binding) } end - def fetch_paginated(query, params, :offset) do + def fetch_paginated(query, params, :offset, table_binding) do options = cast_params(params) query - |> paginate(options, :offset) + |> paginate(options, :offset, table_binding) |> Repo.all() end - def paginate(query, options, method \\ :keyset) + def paginate(query, options, method \\ :keyset, table_binding \\ nil) - def paginate(query, options, :keyset) do + def paginate(query, options, :keyset, table_binding) do query - |> restrict(:min_id, options) - |> restrict(:since_id, options) - |> restrict(:max_id, options) - |> restrict(:order, options) - |> restrict(:limit, options) + |> restrict(:min_id, options, table_binding) + |> restrict(:since_id, options, table_binding) + |> restrict(:max_id, options, table_binding) + |> restrict(:order, options, table_binding) + |> restrict(:limit, options, table_binding) end - def paginate(query, options, :offset) do + def paginate(query, options, :offset, table_binding) do query - |> restrict(:order, options) - |> restrict(:offset, options) - |> restrict(:limit, options) + |> restrict(:order, options, table_binding) + |> restrict(:offset, options, table_binding) + |> restrict(:limit, options, table_binding) end defp cast_params(params) do @@ -75,7 +81,8 @@ defp cast_params(params) do since_id: :string, max_id: :string, offset: :integer, - limit: :integer + limit: :integer, + skip_order: :boolean } params = @@ -88,38 +95,48 @@ defp cast_params(params) do changeset.changes end - defp restrict(query, :min_id, %{min_id: min_id}) do - where(query, [q], q.id > ^min_id) + defp restrict(query, :min_id, %{min_id: min_id}, table_binding) do + where(query, [{q, table_position(query, table_binding)}], q.id > ^min_id) end - defp restrict(query, :since_id, %{since_id: since_id}) do - where(query, [q], q.id > ^since_id) + defp restrict(query, :since_id, %{since_id: since_id}, table_binding) do + where(query, [{q, table_position(query, table_binding)}], q.id > ^since_id) end - defp restrict(query, :max_id, %{max_id: max_id}) do - where(query, [q], q.id < ^max_id) + defp restrict(query, :max_id, %{max_id: max_id}, table_binding) do + where(query, [{q, table_position(query, table_binding)}], q.id < ^max_id) end - defp restrict(query, :order, %{min_id: _}) do - order_by(query, [u], fragment("? asc nulls last", u.id)) + defp restrict(query, :order, %{skip_order: true}, _), do: query + + defp restrict(query, :order, %{min_id: _}, table_binding) do + order_by( + query, + [{u, table_position(query, table_binding)}], + fragment("? asc nulls last", u.id) + ) end - defp restrict(query, :order, _options) do - order_by(query, [u], fragment("? desc nulls last", u.id)) + defp restrict(query, :order, _options, table_binding) do + order_by( + query, + [{u, table_position(query, table_binding)}], + fragment("? desc nulls last", u.id) + ) end - defp restrict(query, :offset, %{offset: offset}) do + defp restrict(query, :offset, %{offset: offset}, _table_binding) do offset(query, ^offset) end - defp restrict(query, :limit, options) do + defp restrict(query, :limit, options, _table_binding) do limit = Map.get(options, :limit, @default_limit) query |> limit(^limit) end - defp restrict(query, _, _), do: query + defp restrict(query, _, _, _), do: query defp enforce_order(result, %{min_id: _}) do result @@ -127,4 +144,10 @@ defp enforce_order(result, %{min_id: _}) do end defp enforce_order(result, _), do: result + + defp table_position(%Ecto.Query{} = query, binding_name) do + Map.get(query.aliases, binding_name, 0) + end + + defp table_position(_, _), do: 0 end diff --git a/lib/pleroma/report_note.ex b/lib/pleroma/report_note.ex new file mode 100644 index 000000000..0db86d1a1 --- /dev/null +++ b/lib/pleroma/report_note.ex @@ -0,0 +1,48 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.ReportNote do + use Ecto.Schema + + import Ecto.Changeset + import Ecto.Query + + alias Pleroma.Activity + alias Pleroma.Repo + alias Pleroma.ReportNote + alias Pleroma.User + + @type t :: %__MODULE__{} + + schema "report_notes" do + field(:content, :string) + belongs_to(:user, User, type: FlakeId.Ecto.CompatType) + belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType) + + timestamps() + end + + @spec create(FlakeId.Ecto.CompatType.t(), FlakeId.Ecto.CompatType.t(), String.t()) :: + {:ok, ReportNote.t()} | {:error, Changeset.t()} + def create(user_id, activity_id, content) do + attrs = %{ + user_id: user_id, + activity_id: activity_id, + content: content + } + + %ReportNote{} + |> cast(attrs, [:user_id, :activity_id, :content]) + |> validate_required([:user_id, :activity_id, :content]) + |> Repo.insert() + end + + @spec destroy(FlakeId.Ecto.CompatType.t()) :: + {:ok, ReportNote.t()} | {:error, Changeset.t()} + def destroy(id) do + from(r in ReportNote, where: r.id == ^id) + |> Repo.one() + |> Repo.delete() + end +end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 2bb3ad635..16e6b0057 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1068,6 +1068,13 @@ defp maybe_preload_bookmarks(query, opts) do |> Activity.with_preloaded_bookmark(opts["user"]) end + defp maybe_preload_report_notes(query, %{"preload_report_notes" => true}) do + query + |> Activity.with_preloaded_report_notes() + end + + defp maybe_preload_report_notes(query, _), do: query + defp maybe_set_thread_muted_field(query, %{"skip_preload" => true}), do: query defp maybe_set_thread_muted_field(query, opts) do @@ -1121,6 +1128,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do Activity |> maybe_preload_objects(opts) |> maybe_preload_bookmarks(opts) + |> maybe_preload_report_notes(opts) |> maybe_set_thread_muted_field(opts) |> maybe_order(opts) |> restrict_recipients(recipients, opts["user"]) @@ -1157,6 +1165,25 @@ def fetch_activities(recipients, opts \\ %{}, pagination \\ :keyset) do |> maybe_update_cc(list_memberships, opts["user"]) end + @doc """ + Fetch favorites activities of user with order by sort adds to favorites + """ + @spec fetch_favourites(User.t(), map(), atom()) :: list(Activity.t()) + def fetch_favourites(user, params \\ %{}, pagination \\ :keyset) do + user.ap_id + |> Activity.Queries.by_actor() + |> Activity.Queries.by_type("Like") + |> Activity.with_joined_object() + |> Object.with_joined_activity() + |> select([_like, object, activity], %{activity | object: object}) + |> order_by([like, _, _], desc: like.id) + |> Pagination.fetch_paginated( + Map.merge(params, %{"skip_order" => true}), + pagination, + :object_activity + ) + end + defp maybe_update_cc(activities, list_memberships, %User{ap_id: user_ap_id}) when is_list(list_memberships) and length(list_memberships) > 0 do Enum.map(activities, fn diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 2ca805c09..e87d09134 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -787,6 +787,7 @@ def get_reports(params, page, page_size) do params |> Map.put("type", "Flag") |> Map.put("skip_preload", true) + |> Map.put("preload_report_notes", true) |> Map.put("total", true) |> Map.put("limit", page_size) |> Map.put("offset", (page - 1) * page_size) diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 0a8a56cd8..c8abeff06 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do alias Pleroma.Activity alias Pleroma.ModerationLog alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.ReportNote alias Pleroma.User alias Pleroma.UserInviteToken alias Pleroma.Web.ActivityPub.ActivityPub @@ -240,7 +241,7 @@ def list_instance_statuses(conn, %{"instance" => instance} = params) do }) conn - |> put_view(StatusView) + |> put_view(Pleroma.Web.AdminAPI.StatusView) |> render("index.json", %{activities: activities, as: :activity}) end @@ -643,9 +644,11 @@ def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nic def list_reports(conn, params) do {page, page_size} = page_params(params) + reports = Utils.get_reports(params, page, page_size) + conn |> put_view(ReportView) - |> render("index.json", %{reports: Utils.get_reports(params, page, page_size)}) + |> render("index.json", %{reports: reports}) end def list_grouped_reports(conn, _params) do @@ -689,32 +692,39 @@ def reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) d end end - def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do - with false <- is_nil(params["status"]), - %Activity{} <- Activity.get_by_id(id) do - params = - params - |> Map.put("in_reply_to_status_id", id) - |> Map.put("visibility", "direct") - - {:ok, activity} = CommonAPI.post(user, params) - + def report_notes_create(%{assigns: %{user: user}} = conn, %{ + "id" => report_id, + "content" => content + }) do + with {:ok, _} <- ReportNote.create(user.id, report_id, content) do ModerationLog.insert_log(%{ - action: "report_response", + action: "report_note", actor: user, - subject: activity, - text: params["status"] + subject: Activity.get_by_id(report_id), + text: content }) - conn - |> put_view(StatusView) - |> render("show.json", %{activity: activity}) + json_response(conn, :no_content, "") else - true -> - {:param_cast, nil} + _ -> json_response(conn, :bad_request, "") + end + end - nil -> - {:error, :not_found} + def report_notes_delete(%{assigns: %{user: user}} = conn, %{ + "id" => note_id, + "report_id" => report_id + }) do + with {:ok, note} <- ReportNote.destroy(note_id) do + ModerationLog.insert_log(%{ + action: "report_note_delete", + actor: user, + subject: Activity.get_by_id(report_id), + text: note.content + }) + + json_response(conn, :no_content, "") + else + _ -> json_response(conn, :bad_request, "") end end diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex index 13602efd9..4880d2992 100644 --- a/lib/pleroma/web/admin_api/views/report_view.ex +++ b/lib/pleroma/web/admin_api/views/report_view.ex @@ -39,7 +39,8 @@ def render("show.json", %{report: report, user: user, account: account, statuses content: content, created_at: created_at, statuses: StatusView.render("index.json", %{activities: statuses, as: :activity}), - state: report.data["state"] + state: report.data["state"], + notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes}) } end @@ -69,6 +70,28 @@ def render("index_grouped.json", %{groups: groups}) do } end + def render("index_notes.json", %{notes: notes}) when is_list(notes) do + Enum.map(notes, &render(__MODULE__, "show_note.json", &1)) + end + + def render("index_notes.json", _), do: [] + + def render("show_note.json", %{ + id: id, + content: content, + user_id: user_id, + inserted_at: inserted_at + }) do + user = User.get_by_id(user_id) + + %{ + id: id, + content: content, + user: merge_account_views(user), + created_at: Utils.to_masto_date(inserted_at) + } + end + defp merge_account_views(%User{} = user) do Pleroma.Web.MastodonAPI.AccountView.render("show.json", %{user: user}) |> Map.merge(Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user})) diff --git a/lib/pleroma/web/admin_api/views/status_view.ex b/lib/pleroma/web/admin_api/views/status_view.ex new file mode 100644 index 000000000..6f2b2b09c --- /dev/null +++ b/lib/pleroma/web/admin_api/views/status_view.ex @@ -0,0 +1,42 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.StatusView do + use Pleroma.Web, :view + + require Pleroma.Constants + + alias Pleroma.User + + def render("index.json", opts) do + render_many(opts.activities, __MODULE__, "show.json", opts) + end + + def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do + user = get_user(activity.data["actor"]) + + Pleroma.Web.MastodonAPI.StatusView.render("show.json", opts) + |> Map.merge(%{account: merge_account_views(user)}) + end + + defp merge_account_views(%User{} = user) do + Pleroma.Web.MastodonAPI.AccountView.render("show.json", %{user: user}) + |> Map.merge(Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user})) + end + + defp merge_account_views(_), do: %{} + + defp get_user(ap_id) do + cond do + user = User.get_cached_by_ap_id(ap_id) -> + user + + user = User.get_by_guessed_nickname(ap_id) -> + user + + true -> + User.error_user(ap_id) + end + end +end diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 74b223cf4..1149fb469 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -346,15 +346,11 @@ def context(%{assigns: %{user: user}} = conn, %{"id" => id}) do @doc "GET /api/v1/favourites" def favourites(%{assigns: %{user: user}} = conn, params) do - params = - params - |> Map.put("type", "Create") - |> Map.put("favorited_by", user.ap_id) - |> Map.put("blocking_user", user) - activities = - ActivityPub.fetch_activities([], params) - |> Enum.reverse() + ActivityPub.fetch_favourites( + user, + Map.take(params, Pleroma.Pagination.page_keys()) + ) conn |> add_link_headers(activities) diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index abcf46034..533f82f6c 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -33,8 +33,11 @@ def schemas(conn, _params) do # under software. def raw_nodeinfo do stats = Stats.get_stats() - - quarantined = Config.get([:instance, :quarantined_instances], []) + exclusions = Config.get([:instance, :mrf_transparency_exclusions], []) + + quarantined = + Config.get([:instance, :quarantined_instances], []) + |> Enum.filter(fn x -> !Enum.member?(exclusions, x) end) staff_accounts = User.all_superusers() diff --git a/lib/pleroma/web/oauth/scopes.ex b/lib/pleroma/web/oauth/scopes.ex index 5e04652c2..00da225b9 100644 --- a/lib/pleroma/web/oauth/scopes.ex +++ b/lib/pleroma/web/oauth/scopes.ex @@ -79,7 +79,9 @@ defp authorize_admin_scopes(scopes, app_scopes, %User{} = user) do if user.is_admin || !contains_admin_scopes?(scopes) || !contains_admin_scopes?(app_scopes) do {:ok, scopes} else - {:error, :unsupported_scopes} + # Gracefully dropping admin scopes from requested scopes if user isn't an admin (not raising) + scopes = scopes -- OAuthScopesPlug.filter_descendants(scopes, ["admin"]) + validate(scopes, app_scopes, user) end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 78cb703a9..f6c128283 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -187,7 +187,8 @@ defmodule Pleroma.Web.Router do get("/grouped_reports", AdminAPIController, :list_grouped_reports) get("/reports/:id", AdminAPIController, :report_show) patch("/reports", AdminAPIController, :reports_update) - post("/reports/:id/respond", AdminAPIController, :report_respond) + post("/reports/:id/notes", AdminAPIController, :report_notes_create) + delete("/reports/:report_id/notes/:id", AdminAPIController, :report_notes_delete) put("/statuses/:id", AdminAPIController, :status_update) delete("/statuses/:id", AdminAPIController, :status_delete) diff --git a/mix.lock b/mix.lock index b008f2b55..b22918156 100644 --- a/mix.lock +++ b/mix.lock @@ -36,8 +36,8 @@ "ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm"}, "ex_syslogger": {:git, "https://github.com/slashmili/ex_syslogger.git", "f3963399047af17e038897c69e20d552e6899e1d", [tag: "1.4.0"]}, "excoveralls": {:hex, :excoveralls, "0.11.2", "0c6f2c8db7683b0caa9d490fb8125709c54580b4255ffa7ad35f3264b075a643", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, - "fast_html": {:hex, :fast_html, "0.99.4", "d80812664f0429607e1d880fba0ef04da87a2e4fa596701bcaae17953535695c", [:make, :mix], [], "hexpm"}, - "fast_sanitize": {:hex, :fast_sanitize, "0.1.4", "6c2e7203ca2f8275527a3021ba6e9d5d4ee213a47dc214a97c128737c9e56df1", [:mix], [{:fast_html, "~> 0.99", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, + "fast_html": {:hex, :fast_html, "1.0.1", "5bc7df4dc4607ec2c314c16414e4111d79a209956c4f5df96602d194c61197f9", [:make, :mix], [], "hexpm"}, + "fast_sanitize": {:hex, :fast_sanitize, "0.1.6", "60a5ae96879956dea409a91a77f5dd2994c24cc10f80eefd8f9892ee4c0c7b25", [:mix], [{:fast_html, "~> 1.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, "floki": {:hex, :floki, "0.23.1", "e100306ce7d8841d70a559748e5091542e2cfc67ffb3ade92b89a8435034dab1", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm"}, "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm"}, diff --git a/priv/repo/migrations/20191203043610_create_report_notes.exs b/priv/repo/migrations/20191203043610_create_report_notes.exs new file mode 100644 index 000000000..a4f8c096d --- /dev/null +++ b/priv/repo/migrations/20191203043610_create_report_notes.exs @@ -0,0 +1,13 @@ +defmodule Pleroma.Repo.Migrations.CreateReportNotes do + use Ecto.Migration + + def change do + create_if_not_exists table(:report_notes) do + add(:user_id, references(:users, type: :uuid)) + add(:activity_id, references(:activities, type: :uuid)) + add(:content, :string) + + timestamps() + end + end +end diff --git a/priv/static/index.html b/priv/static/index.html index 84ae2e3ce..e74f5b5d5 100644 --- a/priv/static/index.html +++ b/priv/static/index.html @@ -1 +1 @@ -Pleroma
\ No newline at end of file +Pleroma
\ No newline at end of file diff --git a/priv/static/instance/panel.html b/priv/static/instance/panel.html index 8fb6f0e74..fd34b7502 100644 --- a/priv/static/instance/panel.html +++ b/priv/static/instance/panel.html @@ -3,7 +3,7 @@

Sadposting admin is @FloatingGhost@ihatebeinga.live

-

Registrations are currently disabled, but if you want an account feel free to message me via any method listed on my profile, or discord@SadMeme#8080 (I very rarely use this so I might be a bit slow)

+

Registrations are currently disabled, but if you want an account feel free to message me via any method listed on my profile, or just @ me


Frontends: diff --git a/priv/static/static/css/app.fd71461124f3eb029b1b.css b/priv/static/static/css/app.ae04505b31bb0ee2765e.css similarity index 97% rename from priv/static/static/css/app.fd71461124f3eb029b1b.css rename to priv/static/static/css/app.ae04505b31bb0ee2765e.css index 6ab9849ec..05e3f2087 100644 Binary files a/priv/static/static/css/app.fd71461124f3eb029b1b.css and b/priv/static/static/css/app.ae04505b31bb0ee2765e.css differ diff --git a/priv/static/static/css/app.fd71461124f3eb029b1b.css.map b/priv/static/static/css/app.ae04505b31bb0ee2765e.css.map similarity index 95% rename from priv/static/static/css/app.fd71461124f3eb029b1b.css.map rename to priv/static/static/css/app.ae04505b31bb0ee2765e.css.map index 660b09d2c..72f33316e 100644 --- a/priv/static/static/css/app.fd71461124f3eb029b1b.css.map +++ b/priv/static/static/css/app.ae04505b31bb0ee2765e.css.map @@ -1 +1 @@ -{"version":3,"sources":["webpack:///./src/hocs/with_load_more/with_load_more.scss","webpack:///./src/components/tab_switcher/tab_switcher.scss","webpack:///./src/hocs/with_subscription/with_subscription.scss"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C;ACTA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C;AClFA;AACA;AACA;AACA;AACA;AACA;AACA,C","file":"static/css/app.fd71461124f3eb029b1b.css","sourcesContent":[".with-load-more-footer {\n padding: 10px;\n text-align: center;\n border-top: 1px solid;\n border-top-color: #222;\n border-top-color: var(--border, #222);\n}\n.with-load-more-footer .error {\n font-size: 14px;\n}",".tab-switcher {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher .contents {\n -ms-flex: 1 0 auto;\n flex: 1 0 auto;\n min-height: 0px;\n}\n.tab-switcher .contents .hidden {\n display: none;\n}\n.tab-switcher .contents.scrollable-tabs {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n overflow-y: auto;\n}\n.tab-switcher .tabs {\n display: -ms-flexbox;\n display: flex;\n position: relative;\n width: 100%;\n overflow-y: hidden;\n overflow-x: auto;\n padding-top: 5px;\n box-sizing: border-box;\n}\n.tab-switcher .tabs::after, .tab-switcher .tabs::before {\n display: block;\n content: \"\";\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}\n.tab-switcher .tabs .tab-wrapper {\n height: 28px;\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n}\n.tab-switcher .tabs .tab-wrapper .tab {\n width: 100%;\n min-width: 1px;\n position: relative;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n padding: 6px 1em;\n padding-bottom: 99px;\n margin-bottom: -93px;\n white-space: nowrap;\n}\n.tab-switcher .tabs .tab-wrapper .tab:not(.active) {\n z-index: 4;\n}\n.tab-switcher .tabs .tab-wrapper .tab:not(.active):hover {\n z-index: 6;\n}\n.tab-switcher .tabs .tab-wrapper .tab.active {\n background: transparent;\n z-index: 5;\n}\n.tab-switcher .tabs .tab-wrapper .tab img {\n max-height: 26px;\n vertical-align: top;\n margin-top: -5px;\n}\n.tab-switcher .tabs .tab-wrapper:not(.active)::after {\n content: \"\";\n position: absolute;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 7;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}",".with-subscription-loading {\n padding: 10px;\n text-align: center;\n}\n.with-subscription-loading .error {\n font-size: 14px;\n}"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack:///./src/hocs/with_load_more/with_load_more.scss","webpack:///./src/components/tab_switcher/tab_switcher.scss","webpack:///./src/hocs/with_subscription/with_subscription.scss"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C;ACTA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C;AClFA;AACA;AACA;AACA;AACA;AACA;AACA,C","file":"static/css/app.ae04505b31bb0ee2765e.css","sourcesContent":[".with-load-more-footer {\n padding: 10px;\n text-align: center;\n border-top: 1px solid;\n border-top-color: #222;\n border-top-color: var(--border, #222);\n}\n.with-load-more-footer .error {\n font-size: 14px;\n}",".tab-switcher {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher .contents {\n -ms-flex: 1 0 auto;\n flex: 1 0 auto;\n min-height: 0px;\n}\n.tab-switcher .contents .hidden {\n display: none;\n}\n.tab-switcher .contents.scrollable-tabs {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n overflow-y: auto;\n}\n.tab-switcher .tabs {\n display: -ms-flexbox;\n display: flex;\n position: relative;\n width: 100%;\n overflow-y: hidden;\n overflow-x: auto;\n padding-top: 5px;\n box-sizing: border-box;\n}\n.tab-switcher .tabs::after, .tab-switcher .tabs::before {\n display: block;\n content: \"\";\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}\n.tab-switcher .tabs .tab-wrapper {\n height: 28px;\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n}\n.tab-switcher .tabs .tab-wrapper .tab {\n width: 100%;\n min-width: 1px;\n position: relative;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n padding: 6px 1em;\n padding-bottom: 99px;\n margin-bottom: -93px;\n white-space: nowrap;\n}\n.tab-switcher .tabs .tab-wrapper .tab:not(.active) {\n z-index: 4;\n}\n.tab-switcher .tabs .tab-wrapper .tab:not(.active):hover {\n z-index: 6;\n}\n.tab-switcher .tabs .tab-wrapper .tab.active {\n background: transparent;\n z-index: 5;\n}\n.tab-switcher .tabs .tab-wrapper .tab img {\n max-height: 26px;\n vertical-align: top;\n margin-top: -5px;\n}\n.tab-switcher .tabs .tab-wrapper:not(.active)::after {\n content: \"\";\n position: absolute;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 7;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}",".with-subscription-loading {\n padding: 10px;\n text-align: center;\n}\n.with-subscription-loading .error {\n font-size: 14px;\n}"],"sourceRoot":""} \ No newline at end of file diff --git a/priv/static/static/font/fontello.1576166651574.eot b/priv/static/static/font/fontello.1576166651574.eot new file mode 100644 index 000000000..fb27d4037 Binary files /dev/null and b/priv/static/static/font/fontello.1576166651574.eot differ diff --git a/priv/static/static/font/fontello.1576166651574.svg b/priv/static/static/font/fontello.1576166651574.svg new file mode 100644 index 000000000..f5e497ce4 --- /dev/null +++ b/priv/static/static/font/fontello.1576166651574.svg @@ -0,0 +1,104 @@ + + + +Copyright (C) 2019 by original authors @ fontello.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/priv/static/static/font/fontello.1576166651574.ttf b/priv/static/static/font/fontello.1576166651574.ttf new file mode 100644 index 000000000..c49743ec6 Binary files /dev/null and b/priv/static/static/font/fontello.1576166651574.ttf differ diff --git a/priv/static/static/font/fontello.1576166651574.woff b/priv/static/static/font/fontello.1576166651574.woff new file mode 100644 index 000000000..bbffd6413 Binary files /dev/null and b/priv/static/static/font/fontello.1576166651574.woff differ diff --git a/priv/static/static/font/fontello.1576166651574.woff2 b/priv/static/static/font/fontello.1576166651574.woff2 new file mode 100644 index 000000000..d35dce862 Binary files /dev/null and b/priv/static/static/font/fontello.1576166651574.woff2 differ diff --git a/priv/static/static/fontello.1576166651574.css b/priv/static/static/fontello.1576166651574.css new file mode 100644 index 000000000..54f9fe05f Binary files /dev/null and b/priv/static/static/fontello.1576166651574.css differ diff --git a/priv/static/static/fontello.json b/priv/static/static/fontello.json new file mode 100755 index 000000000..c0cf17271 --- /dev/null +++ b/priv/static/static/fontello.json @@ -0,0 +1,308 @@ +{ + "name": "", + "css_prefix_text": "icon-", + "css_use_suffix": false, + "hinting": true, + "units_per_em": 1000, + "ascent": 857, + "glyphs": [ + { + "uid": "9bd60140934a1eb9236fd7a8ab1ff6ba", + "css": "spin4", + "code": 59444, + "src": "fontelico" + }, + { + "uid": "5211af474d3a9848f67f945e2ccaf143", + "css": "cancel", + "code": 59392, + "src": "fontawesome" + }, + { + "uid": "eeec3208c90b7b48e804919d0d2d4a41", + "css": "upload", + "code": 59393, + "src": "fontawesome" + }, + { + "uid": "2a6740fc2f9d0edea54205963f662594", + "css": "spin3", + "code": 59442, + "src": "fontelico" + }, + { + "uid": "c6be5a58ee4e63a5ec399c2b0d15cf2c", + "css": "reply", + "code": 61714, + "src": "fontawesome" + }, + { + "uid": "474656633f79ea2f1dad59ff63f6bf07", + "css": "star", + "code": 59394, + "src": "fontawesome" + }, + { + "uid": "d17030afaecc1e1c22349b99f3c4992a", + "css": "star-empty", + "code": 59395, + "src": "fontawesome" + }, + { + "uid": "09feb4465d9bd1364f4e301c9ddbaa92", + "css": "retweet", + "code": 59396, + "src": "fontawesome" + }, + { + "uid": "7fd683b2c518ceb9e5fa6757f2276faa", + "css": "eye-off", + "code": 59397, + "src": "fontawesome" + }, + { + "uid": "73ffeb70554099177620847206c12457", + "css": "binoculars", + "code": 61925, + "src": "fontawesome" + }, + { + "uid": "e99461abfef3923546da8d745372c995", + "css": "cog", + "code": 59399, + "src": "fontawesome" + }, + { + "uid": "1bafeeb1808a5fe24484c7890096901a", + "css": "user-plus", + "code": 62004, + "src": "fontawesome" + }, + { + "uid": "559647a6f430b3aeadbecd67194451dd", + "css": "menu", + "code": 61641, + "src": "fontawesome" + }, + { + "uid": "0d20938846444af8deb1920dc85a29fb", + "css": "logout", + "code": 59400, + "src": "fontawesome" + }, + { + "uid": "ccddff8e8670dcd130e3cb55fdfc2fd0", + "css": "down-open", + "code": 59401, + "src": "fontawesome" + }, + { + "uid": "44b9e75612c5fad5505edd70d071651f", + "css": "attach", + "code": 59402, + "src": "entypo" + }, + { + "uid": "e15f0d620a7897e2035c18c80142f6d9", + "css": "link-ext", + "code": 61582, + "src": "fontawesome" + }, + { + "uid": "e35de5ea31cd56970498e33efbcb8e36", + "css": "link-ext-alt", + "code": 61583, + "src": "fontawesome" + }, + { + "uid": "381da2c2f7fd51f8de877c044d7f439d", + "css": "picture", + "code": 59403, + "src": "fontawesome" + }, + { + "uid": "872d9516df93eb6b776cc4d94bd97dac", + "css": "video", + "code": 59404, + "src": "fontawesome" + }, + { + "uid": "399ef63b1e23ab1b761dfbb5591fa4da", + "css": "right-open", + "code": 59405, + "src": "fontawesome" + }, + { + "uid": "d870630ff8f81e6de3958ecaeac532f2", + "css": "left-open", + "code": 59406, + "src": "fontawesome" + }, + { + "uid": "fe6697b391355dec12f3d86d6d490397", + "css": "up-open", + "code": 59407, + "src": "fontawesome" + }, + { + "uid": "9c1376672bb4f1ed616fdd78a23667e9", + "css": "comment-empty", + "code": 61669, + "src": "fontawesome" + }, + { + "uid": "ccc2329632396dc096bb638d4b46fb98", + "css": "mail-alt", + "code": 61664, + "src": "fontawesome" + }, + { + "uid": "c1f1975c885aa9f3dad7810c53b82074", + "css": "lock", + "code": 59409, + "src": "fontawesome" + }, + { + "uid": "05376be04a27d5a46e855a233d6e8508", + "css": "lock-open-alt", + "code": 61758, + "src": "fontawesome" + }, + { + "uid": "197375a3cea8cb90b02d06e4ddf1433d", + "css": "globe", + "code": 59410, + "src": "fontawesome" + }, + { + "uid": "b3a9e2dab4d19ea3b2f628242c926bfe", + "css": "brush", + "code": 59411, + "src": "iconic" + }, + { + "uid": "9dd9e835aebe1060ba7190ad2b2ed951", + "css": "search", + "code": 59398, + "src": "fontawesome" + }, + { + "uid": "ca90da02d2c6a3183f2458e4dc416285", + "css": "adjust", + "code": 59414, + "src": "fontawesome" + }, + { + "uid": "5e2ab018e3044337bcef5f7e94098ea1", + "css": "thumbs-up-alt", + "code": 61796, + "src": "fontawesome" + }, + { + "uid": "c76b7947c957c9b78b11741173c8349b", + "css": "attention", + "code": 59412, + "src": "fontawesome" + }, + { + "uid": "1a5cfa186647e8c929c2b17b9fc4dac1", + "css": "plus-squared", + "code": 61694, + "src": "fontawesome" + }, + { + "uid": "44e04715aecbca7f266a17d5a7863c68", + "css": "plus", + "code": 59413, + "src": "fontawesome" + }, + { + "uid": "41087bc74d4b20b55059c60a33bf4008", + "css": "edit", + "code": 59415, + "src": "fontawesome" + }, + { + "uid": "5717236f6134afe2d2a278a5c9b3927a", + "css": "play-circled", + "code": 61764, + "src": "fontawesome" + }, + { + "uid": "d35a1d35efeb784d1dc9ac18b9b6c2b6", + "css": "pencil", + "code": 59416, + "src": "fontawesome" + }, + { + "uid": "266d5d9adf15a61800477a5acf9a4462", + "css": "chart-bar", + "code": 59419, + "src": "fontawesome" + }, + { + "uid": "d862a10e1448589215be19702f98f2c1", + "css": "smile", + "code": 61720, + "src": "fontawesome" + }, + { + "uid": "671f29fa10dda08074a4c6a341bb4f39", + "css": "bell-alt", + "code": 61683, + "src": "fontawesome" + }, + { + "uid": "5bb103cd29de77e0e06a52638527b575", + "css": "wrench", + "code": 59418, + "src": "fontawesome" + }, + { + "uid": "5b0772e9484a1a11646793a82edd622a", + "css": "pin", + "code": 59417, + "src": "fontawesome" + }, + { + "uid": "22411a88489225a018f68db737de3c77", + "css": "ellipsis", + "code": 61761, + "src": "custom_icons", + "selected": true, + "svg": { + "path": "M214 411V518Q214 540 199 556T161 571H54Q31 571 16 556T0 518V411Q0 388 16 373T54 357H161Q183 357 199 373T214 411ZM500 411V518Q500 540 484 556T446 571H339Q317 571 301 556T286 518V411Q286 388 301 373T339 357H446Q469 357 484 373T500 411ZM786 411V518Q786 540 770 556T732 571H625Q603 571 587 556T571 518V411Q571 388 587 373T625 357H732Q755 357 770 373T786 411Z", + "width": 785.7 + }, + "search": [ + "ellipsis" + ] + }, + { + "uid": "0bef873af785ead27781fdf98b3ae740", + "css": "bell-ringing-o", + "code": 59408, + "src": "custom_icons", + "selected": true, + "svg": { + "path": "M497.8 0C468.3 0 444.4 23.9 444.4 53.3 444.4 61.1 446.1 68.3 448.9 75 301.7 96.7 213.3 213.3 213.3 320 213.3 588.3 117.8 712.8 35.6 782.2 35.6 821.1 67.8 853.3 106.7 853.3H355.6C355.6 931.7 419.4 995.6 497.8 995.6S640 931.7 640 853.3H888.9C927.8 853.3 960 821.1 960 782.2 877.8 712.8 782.2 588.3 782.2 320 782.2 213.3 693.9 96.7 546.7 75 549.4 68.3 551.1 61.1 551.1 53.3 551.1 23.9 527.2 0 497.8 0ZM189.4 44.8C108.4 118.6 70.5 215.1 71.1 320.2L142.2 319.8C141.7 231.2 170.4 158.3 237.3 97.4L189.4 44.8ZM806.2 44.8L758.3 97.4C825.2 158.3 853.9 231.2 853.3 319.8L924.4 320.2C925.1 215.1 887.2 118.6 806.2 44.8ZM408.9 844.4C413.9 844.4 417.8 848.3 417.8 853.3 417.8 897.2 453.9 933.3 497.8 933.3 502.8 933.3 506.7 937.2 506.7 942.2S502.8 951.1 497.8 951.1C443.9 951.1 400 907.2 400 853.3 400 848.3 403.9 844.4 408.9 844.4Z", + "width": 1000 + }, + "search": [ + "bell-ringing-o" + ] + }, + { + "uid": "0b2b66e526028a6972d51a6f10281b4b", + "css": "zoom-in", + "code": 59420, + "src": "fontawesome" + }, + { + "uid": "0bda4bc779d4c32623dec2e43bd67ee8", + "css": "gauge", + "code": 61668, + "src": "fontawesome" + } + ] +} \ No newline at end of file diff --git a/priv/static/static/js/app.b2fc47df23c3df3426bd.js b/priv/static/static/js/app.a48779d14f1bf30f0cb2.js similarity index 92% rename from priv/static/static/js/app.b2fc47df23c3df3426bd.js rename to priv/static/static/js/app.a48779d14f1bf30f0cb2.js index 05242f619..2828ccd93 100644 Binary files a/priv/static/static/js/app.b2fc47df23c3df3426bd.js and b/priv/static/static/js/app.a48779d14f1bf30f0cb2.js differ diff --git a/priv/static/static/js/app.b2fc47df23c3df3426bd.js.map b/priv/static/static/js/app.a48779d14f1bf30f0cb2.js.map similarity index 99% rename from priv/static/static/js/app.b2fc47df23c3df3426bd.js.map rename to priv/static/static/js/app.a48779d14f1bf30f0cb2.js.map index 76a7618eb..b59b33065 100644 Binary files a/priv/static/static/js/app.b2fc47df23c3df3426bd.js.map and b/priv/static/static/js/app.a48779d14f1bf30f0cb2.js.map differ diff --git a/priv/static/static/js/vendors~app.76db8e4cdf29decd5cab.js b/priv/static/static/js/vendors~app.c2e8b58fd92bd16e962b.js similarity index 91% rename from priv/static/static/js/vendors~app.76db8e4cdf29decd5cab.js rename to priv/static/static/js/vendors~app.c2e8b58fd92bd16e962b.js index 135bdebb3..154a9cbfd 100644 Binary files a/priv/static/static/js/vendors~app.76db8e4cdf29decd5cab.js and b/priv/static/static/js/vendors~app.c2e8b58fd92bd16e962b.js differ diff --git a/priv/static/static/js/vendors~app.76db8e4cdf29decd5cab.js.map b/priv/static/static/js/vendors~app.c2e8b58fd92bd16e962b.js.map similarity index 99% rename from priv/static/static/js/vendors~app.76db8e4cdf29decd5cab.js.map rename to priv/static/static/js/vendors~app.c2e8b58fd92bd16e962b.js.map index 6513c0a0b..244946fa1 100644 Binary files a/priv/static/static/js/vendors~app.76db8e4cdf29decd5cab.js.map and b/priv/static/static/js/vendors~app.c2e8b58fd92bd16e962b.js.map differ diff --git a/priv/static/static/terms-of-service.html b/priv/static/static/terms-of-service.html index 7a73b67fe..e3823b032 100644 --- a/priv/static/static/terms-of-service.html +++ b/priv/static/static/terms-of-service.html @@ -1,15 +1,15 @@

Terms of Service

-

I'm pretty lax provided you're nice

+

It's mainly "be nice"

  1. Don't be a big meanie

    -

    Arguments are cool and all but don't resort to stuff like flamewars. Try to act in good faith - we want to be at least on good terms with people. Please act with understanding towards others on this instance. Most people here are probably struggling with a lot, be mindful of that.

    +

    Arguments are cool and all but don't make them into flamewars. Try to act in good faith - we want to be at least on good terms with people. Please act with understanding towards others on this instance. Most people here are probably struggling with a lot, be mindful of that.

  2. Mark your lewds!

    -

    Reminder that lewd is bad and nobody wants to be forced to see that. Just mark it sensitive, and post unlisted. That is to say, anything suggestive/ecchi upwards should be marked. If you wouldn't look at it with your parents/boss in the room, mark it.

    +

    Reminder that lewd is bad and nobody wants to be forced to see that. Just mark it sensitive, and post unlisted. That is to say, anything suggestive/ecchi upwards should be marked. If you wouldn't look at it with your parents/boss in the room, mark it. It goes without saying that if you're going to post lewd stuff, keep it sensible. Obviously nothing underaged or otherwise questionable. Or you could just not post lewd stuff. Either/or.

  3. This is a Kink Shame Zone

    diff --git a/priv/static/sw-pleroma.js b/priv/static/sw-pleroma.js index 172dd9e29..40f700f90 100644 Binary files a/priv/static/sw-pleroma.js and b/priv/static/sw-pleroma.js differ diff --git a/priv/static/sw.js b/priv/static/sw.js index c2de0cfe0..5605bb05e 100644 Binary files a/priv/static/sw.js and b/priv/static/sw.js differ diff --git a/test/moderation_log_test.exs b/test/moderation_log_test.exs index 4240f6a65..f2168b735 100644 --- a/test/moderation_log_test.exs +++ b/test/moderation_log_test.exs @@ -214,7 +214,7 @@ test "logging report response", %{moderator: moderator} do {:ok, _} = ModerationLog.insert_log(%{ actor: moderator, - action: "report_response", + action: "report_note", subject: report, text: "look at this" }) @@ -222,7 +222,7 @@ test "logging report response", %{moderator: moderator} do log = Repo.one(ModerationLog) assert log.data["message"] == - "@#{moderator.nickname} responded with 'look at this' to report ##{report.id}" + "@#{moderator.nickname} added note 'look at this' to report ##{report.id}" end test "logging status sensitivity update", %{moderator: moderator} do diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 8ae0f45d0..ad1fb6d02 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -1625,6 +1625,38 @@ test "detects hidden follows/followers for friendica" do end end + describe "fetch_favourites/3" do + test "returns a favourite activities sorted by adds to favorite" do + user = insert(:user) + other_user = insert(:user) + user1 = insert(:user) + user2 = insert(:user) + {:ok, a1} = CommonAPI.post(user1, %{"status" => "bla"}) + {:ok, _a2} = CommonAPI.post(user2, %{"status" => "traps are happy"}) + {:ok, a3} = CommonAPI.post(user2, %{"status" => "Trees Are "}) + {:ok, a4} = CommonAPI.post(user2, %{"status" => "Agent Smith "}) + {:ok, a5} = CommonAPI.post(user1, %{"status" => "Red or Blue "}) + + {:ok, _, _} = CommonAPI.favorite(a4.id, user) + {:ok, _, _} = CommonAPI.favorite(a3.id, other_user) + Process.sleep(1000) + {:ok, _, _} = CommonAPI.favorite(a3.id, user) + {:ok, _, _} = CommonAPI.favorite(a5.id, other_user) + Process.sleep(1000) + {:ok, _, _} = CommonAPI.favorite(a5.id, user) + {:ok, _, _} = CommonAPI.favorite(a4.id, other_user) + Process.sleep(1000) + {:ok, _, _} = CommonAPI.favorite(a1.id, user) + {:ok, _, _} = CommonAPI.favorite(a1.id, other_user) + result = ActivityPub.fetch_favourites(user) + + assert Enum.map(result, & &1.id) == [a1.id, a5.id, a3.id, a4.id] + + result = ActivityPub.fetch_favourites(user, %{"limit" => 2}) + assert Enum.map(result, & &1.id) == [a1.id, a5.id] + end + end + describe "Move activity" do test "create" do %{ap_id: old_ap_id} = old_user = insert(:user) diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index 23ca7f110..49ff005b6 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -10,6 +10,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do alias Pleroma.HTML alias Pleroma.ModerationLog alias Pleroma.Repo + alias Pleroma.ReportNote alias Pleroma.Tests.ObanHelpers alias Pleroma.User alias Pleroma.UserInviteToken @@ -1831,61 +1832,6 @@ test "account not empty if status was deleted", %{ end end - describe "POST /api/pleroma/admin/reports/:id/respond" do - setup %{conn: conn} do - admin = insert(:user, is_admin: true) - - %{conn: assign(conn, :user, admin), admin: admin} - end - - test "returns created dm", %{conn: conn, admin: admin} do - [reporter, target_user] = insert_pair(:user) - activity = insert(:note_activity, user: target_user) - - {:ok, %{id: report_id}} = - CommonAPI.report(reporter, %{ - "account_id" => target_user.id, - "comment" => "I feel offended", - "status_ids" => [activity.id] - }) - - response = - conn - |> post("/api/pleroma/admin/reports/#{report_id}/respond", %{ - "status" => "I will check it out" - }) - |> json_response(:ok) - - recipients = Enum.map(response["mentions"], & &1["username"]) - - assert reporter.nickname in recipients - assert response["content"] == "I will check it out" - assert response["visibility"] == "direct" - - log_entry = Repo.one(ModerationLog) - - assert ModerationLog.get_log_entry_message(log_entry) == - "@#{admin.nickname} responded with 'I will check it out' to report ##{ - response["id"] - }" - end - - test "returns 400 when status is missing", %{conn: conn} do - conn = post(conn, "/api/pleroma/admin/reports/test/respond") - - assert json_response(conn, :bad_request) == "Invalid parameters" - end - - test "returns 404 when report id is invalid", %{conn: conn} do - conn = - post(conn, "/api/pleroma/admin/reports/test/respond", %{ - "status" => "foo" - }) - - assert json_response(conn, :not_found) == "Not found" - end - end - describe "PUT /api/pleroma/admin/statuses/:id" do setup %{conn: conn} do admin = insert(:user, is_admin: true) @@ -3082,6 +3028,77 @@ test "it resend emails for two users", %{admin: admin} do }" end end + + describe "POST /reports/:id/notes" do + setup do + admin = insert(:user, is_admin: true) + [reporter, target_user] = insert_pair(:user) + activity = insert(:note_activity, user: target_user) + + {:ok, %{id: report_id}} = + CommonAPI.report(reporter, %{ + "account_id" => target_user.id, + "comment" => "I feel offended", + "status_ids" => [activity.id] + }) + + build_conn() + |> assign(:user, admin) + |> post("/api/pleroma/admin/reports/#{report_id}/notes", %{ + content: "this is disgusting!" + }) + + build_conn() + |> assign(:user, admin) + |> post("/api/pleroma/admin/reports/#{report_id}/notes", %{ + content: "this is disgusting2!" + }) + + %{ + admin_id: admin.id, + report_id: report_id, + admin: admin + } + end + + test "it creates report note", %{admin_id: admin_id, report_id: report_id} do + [note, _] = Repo.all(ReportNote) + + assert %{ + activity_id: ^report_id, + content: "this is disgusting!", + user_id: ^admin_id + } = note + end + + test "it returns reports with notes", %{admin: admin} do + conn = + build_conn() + |> assign(:user, admin) + |> get("/api/pleroma/admin/reports") + + response = json_response(conn, 200) + notes = hd(response["reports"])["notes"] + [note, _] = notes + + assert note["user"]["nickname"] == admin.nickname + assert note["content"] == "this is disgusting!" + assert note["created_at"] + assert response["total"] == 1 + end + + test "it deletes the note", %{admin: admin, report_id: report_id} do + assert ReportNote |> Repo.all() |> length() == 2 + + [note, _] = Repo.all(ReportNote) + + build_conn() + |> assign(:user, admin) + |> delete("/api/pleroma/admin/reports/#{report_id}/notes/#{note.id}") + + assert ReportNote |> Repo.all() |> length() == 1 + end + end end # Needed for testing diff --git a/test/web/admin_api/views/report_view_test.exs b/test/web/admin_api/views/report_view_test.exs index ef4a806e4..a0c6eab3c 100644 --- a/test/web/admin_api/views/report_view_test.exs +++ b/test/web/admin_api/views/report_view_test.exs @@ -30,6 +30,7 @@ test "renders a report" do Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: other_user}) ), statuses: [], + notes: [], state: "open", id: activity.id } @@ -65,6 +66,7 @@ test "includes reported statuses" do ), statuses: [StatusView.render("show.json", %{activity: activity})], state: "open", + notes: [], id: report_activity.id } diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs index beb995cd8..901f2ae41 100644 --- a/test/web/oauth/oauth_controller_test.exs +++ b/test/web/oauth/oauth_controller_test.exs @@ -567,33 +567,41 @@ test "with existing authentication and OOB `redirect_uri`, redirects to app with end describe "POST /oauth/authorize" do - test "redirects with oauth authorization" do - user = insert(:user) - app = insert(:oauth_app, scopes: ["read", "write", "follow"]) + test "redirects with oauth authorization, " <> + "keeping only non-admin scopes for non-admin user" do + app = insert(:oauth_app, scopes: ["read", "write", "admin"]) redirect_uri = OAuthController.default_redirect_uri(app) - conn = - build_conn() - |> post("/oauth/authorize", %{ - "authorization" => %{ - "name" => user.nickname, - "password" => "test", - "client_id" => app.client_id, - "redirect_uri" => redirect_uri, - "scope" => "read:subscope write", - "state" => "statepassed" - } - }) + non_admin = insert(:user, is_admin: false) + admin = insert(:user, is_admin: true) - target = redirected_to(conn) - assert target =~ redirect_uri + for {user, expected_scopes} <- %{ + non_admin => ["read:subscope", "write"], + admin => ["read:subscope", "write", "admin"] + } do + conn = + build_conn() + |> post("/oauth/authorize", %{ + "authorization" => %{ + "name" => user.nickname, + "password" => "test", + "client_id" => app.client_id, + "redirect_uri" => redirect_uri, + "scope" => "read:subscope write admin", + "state" => "statepassed" + } + }) - query = URI.parse(target).query |> URI.query_decoder() |> Map.new() + target = redirected_to(conn) + assert target =~ redirect_uri - assert %{"state" => "statepassed", "code" => code} = query - auth = Repo.get_by(Authorization, token: code) - assert auth - assert auth.scopes == ["read:subscope", "write"] + query = URI.parse(target).query |> URI.query_decoder() |> Map.new() + + assert %{"state" => "statepassed", "code" => code} = query + auth = Repo.get_by(Authorization, token: code) + assert auth + assert auth.scopes == expected_scopes + end end test "returns 401 for wrong credentials", %{conn: conn} do @@ -623,31 +631,34 @@ test "returns 401 for wrong credentials", %{conn: conn} do assert result =~ "Invalid Username/Password" end - test "returns 401 for missing scopes", %{conn: conn} do - user = insert(:user) - app = insert(:oauth_app) + test "returns 401 for missing scopes " <> + "(including all admin-only scopes for non-admin user)" do + user = insert(:user, is_admin: false) + app = insert(:oauth_app, scopes: ["read", "write", "admin"]) redirect_uri = OAuthController.default_redirect_uri(app) - result = - conn - |> post("/oauth/authorize", %{ - "authorization" => %{ - "name" => user.nickname, - "password" => "test", - "client_id" => app.client_id, - "redirect_uri" => redirect_uri, - "state" => "statepassed", - "scope" => "" - } - }) - |> html_response(:unauthorized) + for scope_param <- ["", "admin:read admin:write"] do + result = + build_conn() + |> post("/oauth/authorize", %{ + "authorization" => %{ + "name" => user.nickname, + "password" => "test", + "client_id" => app.client_id, + "redirect_uri" => redirect_uri, + "state" => "statepassed", + "scope" => scope_param + } + }) + |> html_response(:unauthorized) - # Keep the details - assert result =~ app.client_id - assert result =~ redirect_uri + # Keep the details + assert result =~ app.client_id + assert result =~ redirect_uri - # Error message - assert result =~ "This action is outside the authorized scopes" + # Error message + assert result =~ "This action is outside the authorized scopes" + end end test "returns 401 for scopes beyond app scopes hierarchy", %{conn: conn} do