Merge remote-tracking branch 'remotes/upstream/develop' into 1149-oban-job-queue

This commit is contained in:
Ivan Tashkinov 2019-09-05 11:30:41 +03:00
commit 79cf629e1a
13 changed files with 138 additions and 51 deletions

View file

@ -11,8 +11,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed ### Changed
- **Breaking:** Configuration: A setting to explicitly disable the mailer was added, defaulting to true, if you are using a mailer add `config :pleroma, Pleroma.Emails.Mailer, enabled: true` to your config - **Breaking:** Configuration: A setting to explicitly disable the mailer was added, defaulting to true, if you are using a mailer add `config :pleroma, Pleroma.Emails.Mailer, enabled: true` to your config
- **Breaking:** Configuration: `/media/` is now removed when `base_url` is configured, append `/media/` to your `base_url` config to keep the old behaviour if desired - **Breaking:** Configuration: `/media/` is now removed when `base_url` is configured, append `/media/` to your `base_url` config to keep the old behaviour if desired
- **Breaking:** `/api/pleroma/notifications/read` is moved to `/api/v1/pleroma/notifications/read` and now supports `max_id` and responds with Mastodon API entities.
- Configuration: OpenGraph and TwitterCard providers enabled by default - Configuration: OpenGraph and TwitterCard providers enabled by default
- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text - Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
- Mastodon API: `pleroma.thread_muted` key in the Status entity
- Federation: Return 403 errors when trying to request pages from a user's follower/following collections if they have `hide_followers`/`hide_follows` set - Federation: Return 403 errors when trying to request pages from a user's follower/following collections if they have `hide_followers`/`hide_follows` set
- NodeInfo: Return `skipThreadContainment` in `metadata` for the `skip_thread_containment` option - NodeInfo: Return `skipThreadContainment` in `metadata` for the `skip_thread_containment` option
- NodeInfo: Return `mailerEnabled` in `metadata` - NodeInfo: Return `mailerEnabled` in `metadata`
@ -33,6 +35,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Federation/MediaProxy not working with instances that have wrong certificate order - Federation/MediaProxy not working with instances that have wrong certificate order
- Mastodon API: Handling of search timeouts (`/api/v1/search` and `/api/v2/search`) - Mastodon API: Handling of search timeouts (`/api/v1/search` and `/api/v2/search`)
- Mastodon API: Embedded relationships not being properly rendered in the Account entity of Status entity - Mastodon API: Embedded relationships not being properly rendered in the Account entity of Status entity
- Mastodon API: Notifications endpoint crashing if one notification failed to render
- Mastodon API: follower/following counters not being nullified, when `hide_follows`/`hide_followers` is set - Mastodon API: follower/following counters not being nullified, when `hide_follows`/`hide_followers` is set
- Mastodon API: `muted` in the Status entity, using author's account to determine if the tread was muted - Mastodon API: `muted` in the Status entity, using author's account to determine if the tread was muted
- Mastodon API: Add `account_id`, `type`, `offset`, and `limit` to search API (`/api/v1/search` and `/api/v2/search`) - Mastodon API: Add `account_id`, `type`, `offset`, and `limit` to search API (`/api/v1/search` and `/api/v2/search`)

View file

@ -1,4 +1,4 @@
FROM rinpatch/elixir:1.9.0-rc.0-alpine as build FROM elixir:1.9-alpine as build
COPY . . COPY . .
@ -12,7 +12,7 @@ RUN apk add git gcc g++ musl-dev make &&\
mkdir release &&\ mkdir release &&\
mix release --path release mix release --path release
FROM alpine:latest FROM alpine:3.9
ARG HOME=/opt/pleroma ARG HOME=/opt/pleroma
ARG DATA=/var/lib/pleroma ARG DATA=/var/lib/pleroma

View file

@ -10,7 +10,7 @@
notify_email: System.get_env("NOTIFY_EMAIL"), notify_email: System.get_env("NOTIFY_EMAIL"),
limit: 5000, limit: 5000,
registrations_open: false, registrations_open: false,
dynamic_configuration: true healthcheck: true
config :pleroma, Pleroma.Repo, config :pleroma, Pleroma.Repo,
adapter: Ecto.Adapters.Postgres, adapter: Ecto.Adapters.Postgres,

View file

@ -26,6 +26,7 @@ Has these additional fields under the `pleroma` object:
- `content`: a map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` - `content`: a map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`
- `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` - `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`
- `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire - `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire
- `thread_muted`: true if the thread the post belongs to is muted
## Attachments ## Attachments

View file

@ -126,13 +126,14 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi
## `/api/pleroma/admin/` ## `/api/pleroma/admin/`
See [Admin-API](Admin-API.md) See [Admin-API](Admin-API.md)
## `/api/pleroma/notifications/read` ## `/api/v1/pleroma/notifications/read`
### Mark a single notification as read ### Mark notifications as read
* Method `POST` * Method `POST`
* Authentication: required * Authentication: required
* Params: * Params (mutually exclusive):
* `id`: notification's id * `id`: a single notification id to read
* Response: JSON. Returns `{"status": "success"}` if the reading was successful, otherwise returns `{"error": "error_msg"}` * `max_id`: read all notifications up to this id
* Response: Notification entity/Array of Notification entities that were read. In case of `max_id`, only the first 80 read notifications will be returned.
## `/api/v1/pleroma/accounts/:id/subscribe` ## `/api/v1/pleroma/accounts/:id/subscribe`
### Subscribe to receive notifications for all statuses posted by a user ### Subscribe to receive notifications for all statuses posted by a user

View file

@ -102,15 +102,33 @@ def set_read_up_to(%{id: user_id} = _user, id) do
n in Notification, n in Notification,
where: n.user_id == ^user_id, where: n.user_id == ^user_id,
where: n.id <= ^id, where: n.id <= ^id,
where: n.seen == false,
update: [ update: [
set: [ set: [
seen: true, seen: true,
updated_at: ^NaiveDateTime.utc_now() updated_at: ^NaiveDateTime.utc_now()
] ]
] ],
# Ideally we would preload object and activities here
# but Ecto does not support preloads in update_all
select: n.id
) )
Repo.update_all(query, []) {_, notification_ids} = Repo.update_all(query, [])
Notification
|> where([n], n.id in ^notification_ids)
|> join(:inner, [n], activity in assoc(n, :activity))
|> join(:left, [n, a], object in Object,
on:
fragment(
"(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
object.data,
a.data
)
)
|> preload([n, a, o], activity: {a, object: o})
|> Repo.all()
end end
def read_one(%User{} = user, notification_id) do def read_one(%User{} = user, notification_id) do

View file

@ -14,7 +14,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MastodonAPI.StatusView
def render("index.json", %{notifications: notifications, for: user}) do def render("index.json", %{notifications: notifications, for: user}) do
render_many(notifications, NotificationView, "show.json", %{for: user}) safe_render_many(notifications, NotificationView, "show.json", %{for: user})
end end
def render("show.json", %{ def render("show.json", %{

View file

@ -299,7 +299,8 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity
content: %{"text/plain" => content_plaintext}, content: %{"text/plain" => content_plaintext},
spoiler_text: %{"text/plain" => summary_plaintext}, spoiler_text: %{"text/plain" => summary_plaintext},
expires_at: expires_at, expires_at: expires_at,
direct_conversation_id: direct_conversation_id direct_conversation_id: direct_conversation_id,
thread_muted: thread_muted?
} }
} }
end end

View file

@ -8,8 +8,10 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 7] import Pleroma.Web.ControllerHelper, only: [add_link_headers: 7]
alias Pleroma.Conversation.Participation alias Pleroma.Conversation.Participation
alias Pleroma.Notification
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.MastodonAPI.ConversationView alias Pleroma.Web.MastodonAPI.ConversationView
alias Pleroma.Web.MastodonAPI.NotificationView
alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MastodonAPI.StatusView
def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do
@ -70,4 +72,27 @@ def update_conversation(
|> render("participation.json", %{participation: participation, for: user}) |> render("participation.json", %{participation: participation, for: user})
end end
end end
def read_notification(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do
with {:ok, notification} <- Notification.read_one(user, notification_id) do
conn
|> put_view(NotificationView)
|> render("show.json", %{notification: notification, for: user})
else
{:error, message} ->
conn
|> put_status(:bad_request)
|> json(%{"error" => message})
end
end
def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do
with notifications <- Notification.set_read_up_to(user, max_id) do
notifications = Enum.take(notifications, 80)
conn
|> put_view(NotificationView)
|> render("index.json", %{notifications: notifications, for: user})
end
end
end end

View file

@ -236,12 +236,6 @@ defmodule Pleroma.Web.Router do
post("/blocks_import", UtilController, :blocks_import) post("/blocks_import", UtilController, :blocks_import)
post("/follow_import", UtilController, :follow_import) post("/follow_import", UtilController, :follow_import)
end end
scope [] do
pipe_through(:oauth_read)
post("/notifications/read", UtilController, :notifications_read)
end
end end
scope "/oauth", Pleroma.Web.OAuth do scope "/oauth", Pleroma.Web.OAuth do
@ -277,6 +271,7 @@ defmodule Pleroma.Web.Router do
scope [] do scope [] do
pipe_through(:oauth_write) pipe_through(:oauth_write)
patch("/conversations/:id", PleromaAPIController, :update_conversation) patch("/conversations/:id", PleromaAPIController, :update_conversation)
post("/notifications/read", PleromaAPIController, :read_notification)
end end
end end

View file

@ -150,7 +150,8 @@ test "a note activity" do
content: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["content"])}, content: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["content"])},
spoiler_text: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["summary"])}, spoiler_text: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["summary"])},
expires_at: nil, expires_at: nil,
direct_conversation_id: nil direct_conversation_id: nil,
thread_muted: false
} }
} }
@ -173,6 +174,24 @@ test "tells if the message is muted for some reason" do
assert status.muted == true assert status.muted == true
end end
test "tells if the message is thread muted" do
user = insert(:user)
other_user = insert(:user)
{:ok, user} = User.mute(user, other_user)
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"})
status = StatusView.render("status.json", %{activity: activity, for: user})
assert status.pleroma.thread_muted == false
{:ok, activity} = CommonAPI.add_mute(user, activity)
status = StatusView.render("status.json", %{activity: activity, for: user})
assert status.pleroma.thread_muted == true
end
test "tells if the status is bookmarked" do test "tells if the status is bookmarked" do
user = insert(:user) user = insert(:user)

View file

@ -6,6 +6,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
use Pleroma.Web.ConnCase use Pleroma.Web.ConnCase
alias Pleroma.Conversation.Participation alias Pleroma.Conversation.Participation
alias Pleroma.Notification
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
@ -91,4 +92,59 @@ test "PATCH /api/v1/pleroma/conversations/:id", %{conn: conn} do
assert user in participation.recipients assert user in participation.recipients
assert other_user in participation.recipients assert other_user in participation.recipients
end end
describe "POST /api/v1/pleroma/notifications/read" do
test "it marks a single notification as read", %{conn: conn} do
user1 = insert(:user)
user2 = insert(:user)
{:ok, activity1} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"})
{:ok, activity2} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"})
{:ok, [notification1]} = Notification.create_notifications(activity1)
{:ok, [notification2]} = Notification.create_notifications(activity2)
response =
conn
|> assign(:user, user1)
|> post("/api/v1/pleroma/notifications/read", %{"id" => "#{notification1.id}"})
|> json_response(:ok)
assert %{"pleroma" => %{"is_seen" => true}} = response
assert Repo.get(Notification, notification1.id).seen
refute Repo.get(Notification, notification2.id).seen
end
test "it marks multiple notifications as read", %{conn: conn} do
user1 = insert(:user)
user2 = insert(:user)
{:ok, _activity1} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"})
{:ok, _activity2} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"})
{:ok, _activity3} = CommonAPI.post(user2, %{"status" => "HIE @#{user1.nickname}"})
[notification3, notification2, notification1] = Notification.for_user(user1, %{limit: 3})
[response1, response2] =
conn
|> assign(:user, user1)
|> post("/api/v1/pleroma/notifications/read", %{"max_id" => "#{notification2.id}"})
|> json_response(:ok)
assert %{"pleroma" => %{"is_seen" => true}} = response1
assert %{"pleroma" => %{"is_seen" => true}} = response2
assert Repo.get(Notification, notification1.id).seen
assert Repo.get(Notification, notification2.id).seen
refute Repo.get(Notification, notification3.id).seen
end
test "it returns error when notification not found", %{conn: conn} do
user1 = insert(:user)
response =
conn
|> assign(:user, user1)
|> post("/api/v1/pleroma/notifications/read", %{"id" => "22222222222222"})
|> json_response(:bad_request)
assert response == %{"error" => "Cannot get notification"}
end
end
end end

View file

@ -6,7 +6,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
use Pleroma.Web.ConnCase use Pleroma.Web.ConnCase
use Oban.Testing, repo: Pleroma.Repo use Oban.Testing, repo: Pleroma.Repo
alias Pleroma.Notification
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.Tests.ObanHelpers alias Pleroma.Tests.ObanHelpers
alias Pleroma.User alias Pleroma.User
@ -143,37 +142,6 @@ test "it imports blocks users from file", %{conn: conn} do
end end
end end
describe "POST /api/pleroma/notifications/read" do
test "it marks a single notification as read", %{conn: conn} do
user1 = insert(:user)
user2 = insert(:user)
{:ok, activity1} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"})
{:ok, activity2} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"})
{:ok, [notification1]} = Notification.create_notifications(activity1)
{:ok, [notification2]} = Notification.create_notifications(activity2)
conn
|> assign(:user, user1)
|> post("/api/pleroma/notifications/read", %{"id" => "#{notification1.id}"})
|> json_response(:ok)
assert Repo.get(Notification, notification1.id).seen
refute Repo.get(Notification, notification2.id).seen
end
test "it returns error when notification not found", %{conn: conn} do
user1 = insert(:user)
response =
conn
|> assign(:user, user1)
|> post("/api/pleroma/notifications/read", %{"id" => "22222222222222"})
|> json_response(403)
assert response == %{"error" => "Cannot get notification"}
end
end
describe "PUT /api/pleroma/notification_settings" do describe "PUT /api/pleroma/notification_settings" do
test "it updates notification settings", %{conn: conn} do test "it updates notification settings", %{conn: conn} do
user = insert(:user) user = insert(:user)