Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into develop
This commit is contained in:
commit
c2a1bac5de
|
@ -11,14 +11,17 @@ 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`
|
||||||
- Mastodon API: Unsubscribe followers when they unfollow a user
|
- Mastodon API: Unsubscribe followers when they unfollow a user
|
||||||
- AdminAPI: Add "godmode" while fetching user statuses (i.e. admin can see private statuses)
|
- AdminAPI: Add "godmode" while fetching user statuses (i.e. admin can see private statuses)
|
||||||
- Improve digest email template
|
- Improve digest email template
|
||||||
|
– Pagination: (optional) return `total` alongside with `items` when paginating
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Following from Osada
|
- Following from Osada
|
||||||
|
@ -29,7 +32,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- `federation_incoming_replies_max_depth` option being ignored in certain cases
|
- `federation_incoming_replies_max_depth` option being ignored in certain cases
|
||||||
- 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: Misskey's endless polls being unable to render
|
||||||
- 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`)
|
||||||
|
@ -49,6 +54,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Reverse Proxy limiting `max_body_length` was incorrectly defined and only checked `Content-Length` headers which may not be sufficient in some circumstances
|
- Reverse Proxy limiting `max_body_length` was incorrectly defined and only checked `Content-Length` headers which may not be sufficient in some circumstances
|
||||||
- MRF: fix use of unserializable keyword lists in describe() implementations
|
- MRF: fix use of unserializable keyword lists in describe() implementations
|
||||||
- ActivityPub: Deactivated user deletion
|
- ActivityPub: Deactivated user deletion
|
||||||
|
- MRF: fix ability to follow a relay when AntiFollowbotPolicy was enabled
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Expiring/ephemeral activites. All activities can have expires_at value set, which controls when they should be deleted automatically.
|
- Expiring/ephemeral activites. All activities can have expires_at value set, which controls when they should be deleted automatically.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -71,26 +71,26 @@ server {
|
||||||
proxy_set_header Connection "upgrade";
|
proxy_set_header Connection "upgrade";
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
|
|
||||||
# this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only
|
# this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only
|
||||||
# and `localhost.` resolves to [::0] on some systems: see issue #930
|
# and `localhost.` resolves to [::0] on some systems: see issue #930
|
||||||
proxy_pass http://127.0.0.1:4000;
|
proxy_pass http://127.0.0.1:4000;
|
||||||
|
|
||||||
client_max_body_size 16m;
|
client_max_body_size 16m;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ ^/(media|proxy) {
|
location ~ ^/(media|proxy) {
|
||||||
proxy_cache pleroma_media_cache;
|
proxy_cache pleroma_media_cache;
|
||||||
slice 1m;
|
slice 1m;
|
||||||
proxy_cache_key $host$uri$is_args$args$slice_range;
|
proxy_cache_key $host$uri$is_args$args$slice_range;
|
||||||
proxy_set_header Range $slice_range;
|
proxy_set_header Range $slice_range;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_cache_valid 200 206 301 304 1h;
|
proxy_cache_valid 200 206 301 304 1h;
|
||||||
proxy_cache_lock on;
|
proxy_cache_lock on;
|
||||||
proxy_ignore_client_abort on;
|
proxy_ignore_client_abort on;
|
||||||
proxy_buffering on;
|
proxy_buffering on;
|
||||||
chunked_transfer_encoding on;
|
chunked_transfer_encoding on;
|
||||||
proxy_ignore_headers Cache-Control;
|
proxy_ignore_headers Cache-Control;
|
||||||
proxy_hide_header Cache-Control;
|
proxy_hide_header Cache-Control;
|
||||||
proxy_pass http://localhost:4000;
|
proxy_pass http://127.0.0.1:4000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -16,6 +16,15 @@ defmodule Pleroma.Pagination do
|
||||||
|
|
||||||
def fetch_paginated(query, params, type \\ :keyset)
|
def fetch_paginated(query, params, type \\ :keyset)
|
||||||
|
|
||||||
|
def fetch_paginated(query, %{"total" => true} = params, :keyset) do
|
||||||
|
total = Repo.aggregate(query, :count, :id)
|
||||||
|
|
||||||
|
%{
|
||||||
|
total: total,
|
||||||
|
items: fetch_paginated(query, Map.drop(params, ["total"]), :keyset)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def fetch_paginated(query, params, :keyset) do
|
def fetch_paginated(query, params, :keyset) do
|
||||||
options = cast_params(params)
|
options = cast_params(params)
|
||||||
|
|
||||||
|
@ -25,6 +34,15 @@ def fetch_paginated(query, params, :keyset) do
|
||||||
|> enforce_order(options)
|
|> enforce_order(options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fetch_paginated(query, %{"total" => true} = params, :offset) do
|
||||||
|
total = Repo.aggregate(query, :count, :id)
|
||||||
|
|
||||||
|
%{
|
||||||
|
total: total,
|
||||||
|
items: fetch_paginated(query, Map.drop(params, ["total"]), :offset)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def fetch_paginated(query, params, :offset) do
|
def fetch_paginated(query, params, :offset) do
|
||||||
options = cast_params(params)
|
options = cast_params(params)
|
||||||
|
|
||||||
|
|
|
@ -25,11 +25,15 @@ defp score_displayname("fedibot"), do: 1.0
|
||||||
defp score_displayname(_), do: 0.0
|
defp score_displayname(_), do: 0.0
|
||||||
|
|
||||||
defp determine_if_followbot(%User{nickname: nickname, name: displayname}) do
|
defp determine_if_followbot(%User{nickname: nickname, name: displayname}) do
|
||||||
# nickname will always be a binary string because it's generated by Pleroma.
|
# nickname will be a binary string except when following a relay
|
||||||
nick_score =
|
nick_score =
|
||||||
nickname
|
if is_binary(nickname) do
|
||||||
|> String.downcase()
|
nickname
|
||||||
|> score_nickname()
|
|> String.downcase()
|
||||||
|
|> score_nickname()
|
||||||
|
else
|
||||||
|
0.0
|
||||||
|
end
|
||||||
|
|
||||||
# displayname will either be a binary string or nil, if a displayname isn't set.
|
# displayname will either be a binary string or nil, if a displayname isn't set.
|
||||||
name_score =
|
name_score =
|
||||||
|
|
|
@ -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", %{
|
||||||
|
|
|
@ -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
|
||||||
|
@ -384,16 +385,27 @@ def render("poll.json", %{object: object} = opts) do
|
||||||
end
|
end
|
||||||
|
|
||||||
if options do
|
if options do
|
||||||
end_time =
|
{end_time, expired} =
|
||||||
(object.data["closed"] || object.data["endTime"])
|
case object.data["closed"] || object.data["endTime"] do
|
||||||
|> NaiveDateTime.from_iso8601!()
|
end_time when is_binary(end_time) ->
|
||||||
|
end_time =
|
||||||
|
(object.data["closed"] || object.data["endTime"])
|
||||||
|
|> NaiveDateTime.from_iso8601!()
|
||||||
|
|
||||||
expired =
|
expired =
|
||||||
end_time
|
end_time
|
||||||
|> NaiveDateTime.compare(NaiveDateTime.utc_now())
|
|> NaiveDateTime.compare(NaiveDateTime.utc_now())
|
||||||
|> case do
|
|> case do
|
||||||
:lt -> true
|
:lt -> true
|
||||||
_ -> false
|
_ -> false
|
||||||
|
end
|
||||||
|
|
||||||
|
end_time = Utils.to_masto_date(end_time)
|
||||||
|
|
||||||
|
{end_time, expired}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{nil, false}
|
||||||
end
|
end
|
||||||
|
|
||||||
voted =
|
voted =
|
||||||
|
@ -420,7 +432,7 @@ def render("poll.json", %{object: object} = opts) do
|
||||||
# Mastodon uses separate ids for polls, but an object can't have
|
# Mastodon uses separate ids for polls, but an object can't have
|
||||||
# more than one poll embedded so object id is fine
|
# more than one poll embedded so object id is fine
|
||||||
id: to_string(object.id),
|
id: to_string(object.id),
|
||||||
expires_at: Utils.to_masto_date(end_time),
|
expires_at: end_time,
|
||||||
expired: expired,
|
expired: expired,
|
||||||
multiple: multiple,
|
multiple: multiple,
|
||||||
votes_count: votes_count,
|
votes_count: votes_count,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
1
test/fixtures/tesla_mock/misskey_poll_no_end_date.json
vendored
Normal file
1
test/fixtures/tesla_mock/misskey_poll_no_end_date.json
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"Hashtag":"as:Hashtag"}],"id":"https://skippers-bin.com/notes/7x9tmrp97i","type":"Question","attributedTo":"https://skippers-bin.com/users/7v1w1r8ce6","summary":null,"content":"<p><a href=\"https://marchgenso.me/users/march\" class=\"mention\">@march@marchgenso.me</a><span> How are your notifications now?<br></span><a href=\"https://skippers-bin.com/notes/7x9tmrp97i\"><span>リモートで結果を表示</span></a></p>","_misskey_content":"@march@marchgenso.me How are your notifications now?\n[リモートで結果を表示](https://skippers-bin.com/notes/7x9tmrp97i)","published":"2019-09-05T05:35:32.541Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://skippers-bin.com/users/7v1w1r8ce6/followers","https://marchgenso.me/users/march"],"inReplyTo":null,"attachment":[],"sensitive":false,"tag":[{"type":"Mention","href":"https://marchgenso.me/users/march","name":"@march@marchgenso.me"}],"_misskey_fallback_content":"<p><a href=\"https://marchgenso.me/users/march\" class=\"mention\">@march@marchgenso.me</a><span> How are your notifications now?<br></span><a href=\"https://skippers-bin.com/notes/7x9tmrp97i\"><span>リモートで結果を表示</span></a><span><br>----------------------------------------<br>0: Working<br>1: Broken af<br>----------------------------------------<br>番号を返信して投票</span></p>","endTime":null,"oneOf":[{"type":"Note","name":"Working","replies":{"type":"Collection","totalItems":0}},{"type":"Note","name":"Broken af","replies":{"type":"Collection","totalItems":1}}]}
|
1
test/fixtures/tesla_mock/sjw.json
vendored
Normal file
1
test/fixtures/tesla_mock/sjw.json
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"Hashtag":"as:Hashtag"}],"type":"Person","id":"https://skippers-bin.com/users/7v1w1r8ce6","inbox":"https://skippers-bin.com/users/7v1w1r8ce6/inbox","outbox":"https://skippers-bin.com/users/7v1w1r8ce6/outbox","followers":"https://skippers-bin.com/users/7v1w1r8ce6/followers","following":"https://skippers-bin.com/users/7v1w1r8ce6/following","featured":"https://skippers-bin.com/users/7v1w1r8ce6/collections/featured","sharedInbox":"https://skippers-bin.com/inbox","endpoints":{"sharedInbox":"https://skippers-bin.com/inbox"},"url":"https://skippers-bin.com/@sjw","preferredUsername":"sjw","name":"It's ya boi sjw :verified:","summary":"<p><span>Admin of skippers-bin.com and neckbeard.xyz<br>For the most part I'm just a normal user. I mostly post animu, lewds, may-mays, and shitposts.<br><br>Not an alt of </span><a href=\"https://skippers-bin.com/@sjw@neckbeard.xyz\" class=\"mention\">@sjw@neckbeard.xyz</a><span> but another main.<br><br>Email/XMPP: neckbeard@rape.lol<br>PGP: d016 b622 75ba bcbc 5b3a fced a7d9 4824 0eb3 9c4e</span></p>","icon":{"type":"Image","url":"https://skippers-bin.com/files/webpublic-21b17f5b-3a83-4f50-8d4f-eda92066aa26","sensitive":false},"image":{"type":"Image","url":"https://skippers-bin.com/files/webpublic-1cd7f961-421e-4c31-aa03-74fb82584308","sensitive":false},"tag":[{"id":"https://skippers-bin.com/emojis/verified","type":"Emoji","name":":verified:","updated":"2019-07-12T02:16:12.088Z","icon":{"type":"Image","mediaType":"image/png","url":"https://skippers-bin.com/files/webpublic-dd10b435-6dad-4602-938b-f69ec0a19f2c"}}],"manuallyApprovesFollowers":false,"publicKey":{"id":"https://skippers-bin.com/users/7v1w1r8ce6/publickey","type":"Key","owner":"https://skippers-bin.com/users/7v1w1r8ce6","publicKeyPem":"-----BEGIN RSA PUBLIC KEY-----\nMIICCgKCAgEAvmp71/A6Oxe1UW/44HK0juAJhrjv9gYhaoslaS9K1FB+BHfIjaE9\n9+W2SKRLnVNYNFSN4JJrSGhX5RUjAsf4tcdRDVcmHl7tp2sgOAZeZz5geULm2sJQ\nwElnGk34jT/xCfX+w/O+7DuX31sU7ZK0B2P7ulNGDQXhrzVO0RMx7HhNcsFcusno\n3kmPyyPT1l+PbM2UNWms599/3yicKtuOzMgzxNeXvuHYtAO19txyPiOeYckQOMmT\nwEVIxypgCgNQ0MNtPLPKQTwOgVbvnN7MN+h3esKeKDcPcGQySkbkjZPaVnA6xCQf\nj58c19wqdCfAS4Effo5/bxVmhLpe0l9HYpV7IMasv2LhFntmSmAxBQzhdz0oTYb1\naNqiyfZdClnzutOiKcrFppADo4rZH9Z1WlPHapahrKbF0GRPN8DjSUsoBxfY9wZs\ntlL056hT4o+EFHYrRGo7KP6X/6aQ9sSsmpE08aVpVuXdwuaoaDlW1KrJ0oOk4lZw\nUNXvjEaN3c+VQAw2CNvkAqLuwrjnw7MdcxEGodEXb6s8VvoSOaiDqT7cexSaZe0R\nliCe/3dqFXpX1UrgRiryI4yc1BrEJIGTanchmP2aUJ2R2pccFsREp23C3vMN3M5b\nHw7fvKbUQHyf6lhRoLCOSCz1xaPutaMJmpwLuJo4wPCHGg9QFBYsqxcCAwEAAQ==\n-----END RSA PUBLIC KEY-----\n"},"isCat":true}
|
78
test/pagination_test.exs
Normal file
78
test/pagination_test.exs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.PaginationTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Pagination
|
||||||
|
|
||||||
|
describe "keyset" do
|
||||||
|
setup do
|
||||||
|
notes = insert_list(5, :note)
|
||||||
|
|
||||||
|
%{notes: notes}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "paginates by min_id", %{notes: notes} do
|
||||||
|
id = Enum.at(notes, 2).id |> Integer.to_string()
|
||||||
|
|
||||||
|
%{total: total, items: paginated} =
|
||||||
|
Pagination.fetch_paginated(Object, %{"min_id" => id, "total" => true})
|
||||||
|
|
||||||
|
assert length(paginated) == 2
|
||||||
|
assert total == 5
|
||||||
|
end
|
||||||
|
|
||||||
|
test "paginates by since_id", %{notes: notes} do
|
||||||
|
id = Enum.at(notes, 2).id |> Integer.to_string()
|
||||||
|
|
||||||
|
%{total: total, items: paginated} =
|
||||||
|
Pagination.fetch_paginated(Object, %{"since_id" => id, "total" => true})
|
||||||
|
|
||||||
|
assert length(paginated) == 2
|
||||||
|
assert total == 5
|
||||||
|
end
|
||||||
|
|
||||||
|
test "paginates by max_id", %{notes: notes} do
|
||||||
|
id = Enum.at(notes, 1).id |> Integer.to_string()
|
||||||
|
|
||||||
|
%{total: total, items: paginated} =
|
||||||
|
Pagination.fetch_paginated(Object, %{"max_id" => id, "total" => true})
|
||||||
|
|
||||||
|
assert length(paginated) == 1
|
||||||
|
assert total == 5
|
||||||
|
end
|
||||||
|
|
||||||
|
test "paginates by min_id & limit", %{notes: notes} do
|
||||||
|
id = Enum.at(notes, 2).id |> Integer.to_string()
|
||||||
|
|
||||||
|
paginated = Pagination.fetch_paginated(Object, %{"min_id" => id, "limit" => 1})
|
||||||
|
|
||||||
|
assert length(paginated) == 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "offset" do
|
||||||
|
setup do
|
||||||
|
notes = insert_list(5, :note)
|
||||||
|
|
||||||
|
%{notes: notes}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "paginates by limit" do
|
||||||
|
paginated = Pagination.fetch_paginated(Object, %{"limit" => 2}, :offset)
|
||||||
|
|
||||||
|
assert length(paginated) == 2
|
||||||
|
end
|
||||||
|
|
||||||
|
test "paginates by limit & offset" do
|
||||||
|
paginated = Pagination.fetch_paginated(Object, %{"limit" => 2, "offset" => 4}, :offset)
|
||||||
|
|
||||||
|
assert length(paginated) == 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -992,6 +992,18 @@ def get("http://example.com/rel_me/null", _, _, _) do
|
||||||
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_null.html")}}
|
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_null.html")}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get("https://skippers-bin.com/notes/7x9tmrp97i", _, _, _) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/tesla_mock/misskey_poll_no_end_date.json")
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get("https://skippers-bin.com/users/7v1w1r8ce6", _, _, _) do
|
||||||
|
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/sjw.json")}}
|
||||||
|
end
|
||||||
|
|
||||||
def get(url, query, body, headers) do
|
def get(url, query, body, headers) do
|
||||||
{:error,
|
{:error,
|
||||||
"Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{
|
"Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
@ -532,6 +551,14 @@ test "detects vote status" do
|
||||||
assert Enum.at(result[:options], 1)[:votes_count] == 1
|
assert Enum.at(result[:options], 1)[:votes_count] == 1
|
||||||
assert Enum.at(result[:options], 2)[:votes_count] == 1
|
assert Enum.at(result[:options], 2)[:votes_count] == 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "does not crash on polls with no end date" do
|
||||||
|
object = Object.normalize("https://skippers-bin.com/notes/7x9tmrp97i")
|
||||||
|
result = StatusView.render("poll.json", %{object: object})
|
||||||
|
|
||||||
|
assert result[:expires_at] == nil
|
||||||
|
assert result[:expired] == false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "embeds a relationship in the account" do
|
test "embeds a relationship in the account" do
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
|
defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
|
||||||
use Pleroma.Web.ConnCase
|
use Pleroma.Web.ConnCase
|
||||||
|
|
||||||
alias Pleroma.Notification
|
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
@ -141,37 +140,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)
|
||||||
|
|
Loading…
Reference in a new issue