Merge remote-tracking branch 'origin/develop' into manifest

This commit is contained in:
Alex Gleason 2021-12-19 11:33:10 -06:00
commit e4f9cb1c1b
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
45 changed files with 838 additions and 66 deletions

View file

@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Allow users to remove their emails if instance does not need email to register - Allow users to remove their emails if instance does not need email to register
### Added ### Added
- `activeMonth` and `activeHalfyear` fields in NodeInfo usage.users object
### Fixed ### Fixed
- Subscription(Bell) Notifications: Don't create from Pipeline Ingested replies - Subscription(Bell) Notifications: Don't create from Pipeline Ingested replies

View file

@ -12,7 +12,7 @@ RUN apk add git gcc g++ musl-dev make cmake file-dev &&\
mkdir release &&\ mkdir release &&\
mix release --path release mix release --path release
FROM alpine:3.11 FROM alpine:3.14
ARG BUILD_DATE ARG BUILD_DATE
ARG VCS_REF ARG VCS_REF
@ -31,8 +31,7 @@ LABEL maintainer="ops@pleroma.social" \
ARG HOME=/opt/pleroma ARG HOME=/opt/pleroma
ARG DATA=/var/lib/pleroma ARG DATA=/var/lib/pleroma
RUN echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories &&\ RUN apk update &&\
apk update &&\
apk add exiftool ffmpeg imagemagick libmagic ncurses postgresql-client &&\ apk add exiftool ffmpeg imagemagick libmagic ncurses postgresql-client &&\
adduser --system --shell /bin/false --home ${HOME} pleroma &&\ adduser --system --shell /bin/false --home ${HOME} pleroma &&\
mkdir -p ${DATA}/uploads &&\ mkdir -p ${DATA}/uploads &&\

View file

@ -394,7 +394,7 @@ defp get_actor(group, users), do: Enum.random(users[group])
defp other_data(actor, content) do defp other_data(actor, content) do
%{host: host} = URI.parse(actor.ap_id) %{host: host} = URI.parse(actor.ap_id)
datetime = DateTime.utc_now() datetime = DateTime.utc_now() |> to_string()
context_id = "https://#{host}/contexts/#{UUID.generate()}" context_id = "https://#{host}/contexts/#{UUID.generate()}"
activity_id = "https://#{host}/activities/#{UUID.generate()}" activity_id = "https://#{host}/activities/#{UUID.generate()}"
object_id = "https://#{host}/objects/#{UUID.generate()}" object_id = "https://#{host}/objects/#{UUID.generate()}"

View file

@ -99,15 +99,16 @@ defp hashtag_fetching(params, user, local_only) do
|> Enum.map(&String.downcase(&1)) |> Enum.map(&String.downcase(&1))
_activities = _activities =
params %{
|> Map.put(:type, "Create") type: "Create",
|> Map.put(:local_only, local_only) local_only: local_only,
|> Map.put(:blocking_user, user) blocking_user: user,
|> Map.put(:muting_user, user) muting_user: user,
|> Map.put(:user, user) user: user,
|> Map.put(:tag, tags) tag: tags,
|> Map.put(:tag_all, tag_all) tag_all: tag_all,
|> Map.put(:tag_reject, tag_reject) tag_reject: tag_reject,
}
|> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities() |> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities()
end end
end end

View file

@ -17,14 +17,14 @@ def run(_args) do
# Let the user make 100 posts # Let the user make 100 posts
1..100 1..100
|> Enum.each(fn i -> CommonAPI.post(user, %{"status" => to_string(i)}) end) |> Enum.each(fn i -> CommonAPI.post(user, %{status: to_string(i)}) end)
# Let 10 random users post # Let 10 random users post
posts = posts =
users users
|> Enum.take_random(10) |> Enum.take_random(10)
|> Enum.map(fn {:ok, random_user} -> |> Enum.map(fn {:ok, random_user} ->
{:ok, activity} = CommonAPI.post(random_user, %{"status" => "."}) {:ok, activity} = CommonAPI.post(random_user, %{status: "."})
activity activity
end) end)
@ -42,7 +42,7 @@ def run(_args) do
|> Conn.assign(:user, reading_user) |> Conn.assign(:user, reading_user)
|> Conn.assign(:skip_link_headers, true) |> Conn.assign(:skip_link_headers, true)
Pleroma.Web.MastodonAPI.AccountController.statuses(conn, %{"id" => user.id}) Pleroma.Web.MastodonAPI.AccountController.statuses(conn, %{id: user.id})
end end
}, },
inputs: %{"user" => user, "no user" => nil}, inputs: %{"user" => user, "no user" => nil},
@ -50,7 +50,7 @@ def run(_args) do
) )
users users
|> Enum.each(fn {:ok, follower, user} -> Pleroma.User.follow(follower, user) end) |> Enum.each(fn {:ok, follower} -> Pleroma.User.follow(follower, user) end)
Benchee.run( Benchee.run(
%{ %{
@ -60,7 +60,7 @@ def run(_args) do
|> Conn.assign(:user, reading_user) |> Conn.assign(:user, reading_user)
|> Conn.assign(:skip_link_headers, true) |> Conn.assign(:skip_link_headers, true)
Pleroma.Web.MastodonAPI.AccountController.statuses(conn, %{"id" => user.id}) Pleroma.Web.MastodonAPI.AccountController.statuses(conn, %{id: user.id})
end end
}, },
inputs: %{"user" => user, "no user" => nil}, inputs: %{"user" => user, "no user" => nil},

View file

@ -4,8 +4,7 @@
# you can enable the server option below. # you can enable the server option below.
config :pleroma, Pleroma.Web.Endpoint, config :pleroma, Pleroma.Web.Endpoint,
http: [port: 4001], http: [port: 4001],
url: [port: 4001], url: [port: 4001]
server: true
# Disable captha for tests # Disable captha for tests
config :pleroma, Pleroma.Captcha, config :pleroma, Pleroma.Captcha,
@ -44,7 +43,7 @@
pool_size: 10 pool_size: 10
# Reduce hash rounds for testing # Reduce hash rounds for testing
config :pbkdf2_elixir, rounds: 1 config :pleroma, :password, iterations: 1
config :tesla, adapter: Tesla.Mock config :tesla, adapter: Tesla.Mock

View file

@ -261,6 +261,46 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
} }
``` ```
## `PATCH /api/v1/pleroma/admin/users/suggest`
### Suggest a user
Adds the user(s) to follower recommendations.
- Params:
- `nicknames`: nicknames array
- Response:
```json
{
users: [
{
// user object
}
]
}
```
## `PATCH /api/v1/pleroma/admin/users/unsuggest`
### Unsuggest a user
Removes the user(s) from follower recommendations.
- Params:
- `nicknames`: nicknames array
- Response:
```json
{
users: [
{
// user object
}
]
}
```
## `GET /api/v1/pleroma/admin/users/:nickname_or_id` ## `GET /api/v1/pleroma/admin/users/:nickname_or_id`
### Retrive the details of a user ### Retrive the details of a user

View file

@ -362,11 +362,9 @@ def following_requests_for_actor(%User{ap_id: ap_id}) do
end end
def restrict_deactivated_users(query) do def restrict_deactivated_users(query) do
deactivated_users = deactivated_users_query = from(u in User.Query.build(%{deactivated: true}), select: u.ap_id)
from(u in User.Query.build(%{deactivated: true}), select: u.ap_id)
|> Repo.all()
Activity.Queries.exclude_authors(query, deactivated_users) from(activity in query, where: activity.actor not in subquery(deactivated_users_query))
end end
defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search

View file

@ -9,7 +9,8 @@
mute: 2, mute: 2,
reblog_mute: 3, reblog_mute: 3,
notification_mute: 4, notification_mute: 4,
inverse_subscription: 5 inverse_subscription: 5,
suggestion_dismiss: 6
) )
defenum(Pleroma.FollowingRelationship.State, defenum(Pleroma.FollowingRelationship.State,

View file

@ -103,6 +103,7 @@ defp load_pack(pack_dir, emoji_groups) do
pack_file = Path.join(pack_dir, "pack.json") pack_file = Path.join(pack_dir, "pack.json")
if File.exists?(pack_file) do if File.exists?(pack_file) do
Logger.info("Loading emoji pack from JSON: #{pack_file}")
contents = Jason.decode!(File.read!(pack_file)) contents = Jason.decode!(File.read!(pack_file))
contents["files"] contents["files"]
@ -115,6 +116,7 @@ defp load_pack(pack_dir, emoji_groups) do
emoji_txt = Path.join(pack_dir, "emoji.txt") emoji_txt = Path.join(pack_dir, "emoji.txt")
if File.exists?(emoji_txt) do if File.exists?(emoji_txt) do
Logger.info("Loading emoji pack from emoji.txt: #{emoji_txt}")
load_from_file(emoji_txt, emoji_groups) load_from_file(emoji_txt, emoji_groups)
else else
extensions = Config.get([:emoji, :pack_extensions]) extensions = Config.get([:emoji, :pack_extensions])

View file

@ -338,6 +338,26 @@ def get_log_entry_message(%ModerationLog{
"@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}" "@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}"
end end
def get_log_entry_message(%ModerationLog{
data: %{
"actor" => %{"nickname" => actor_nickname},
"action" => "add_suggestion",
"subject" => users
}
}) do
"@#{actor_nickname} added suggested users: #{users_to_nicknames_string(users)}"
end
def get_log_entry_message(%ModerationLog{
data: %{
"actor" => %{"nickname" => actor_nickname},
"action" => "remove_suggestion",
"subject" => users
}
}) do
"@#{actor_nickname} removed suggested users: #{users_to_nicknames_string(users)}"
end
def get_log_entry_message(%ModerationLog{ def get_log_entry_message(%ModerationLog{
data: %{ data: %{
"actor" => %{"nickname" => actor_nickname}, "actor" => %{"nickname" => actor_nickname},

View file

@ -148,6 +148,7 @@ defmodule Pleroma.User do
field(:last_active_at, :naive_datetime) field(:last_active_at, :naive_datetime)
field(:disclose_client, :boolean, default: true) field(:disclose_client, :boolean, default: true)
field(:pinned_objects, :map, default: %{}) field(:pinned_objects, :map, default: %{})
field(:is_suggested, :boolean, default: false)
embeds_one( embeds_one(
:notification_settings, :notification_settings,
@ -1676,6 +1677,22 @@ def confirm(%User{is_confirmed: false} = user) do
def confirm(%User{} = user), do: {:ok, user} def confirm(%User{} = user), do: {:ok, user}
def set_suggestion(users, is_suggested) when is_list(users) do
Repo.transaction(fn ->
Enum.map(users, fn user ->
with {:ok, user} <- set_suggestion(user, is_suggested), do: user
end)
end)
end
def set_suggestion(%User{is_suggested: is_suggested} = user, is_suggested), do: {:ok, user}
def set_suggestion(%User{} = user, is_suggested) when is_boolean(is_suggested) do
user
|> change(is_suggested: is_suggested)
|> update_and_set_cache()
end
def update_notification_settings(%User{} = user, settings) do def update_notification_settings(%User{} = user, settings) do
user user
|> cast(%{notification_settings: settings}, []) |> cast(%{notification_settings: settings}, [])
@ -2474,8 +2491,8 @@ def update_last_active_at(%__MODULE__{local: true} = user) do
|> update_and_set_cache() |> update_and_set_cache()
end end
def active_user_count(weeks \\ 4) do def active_user_count(days \\ 30) do
active_after = Timex.shift(NaiveDateTime.utc_now(), weeks: -weeks) active_after = Timex.shift(NaiveDateTime.utc_now(), days: -days)
__MODULE__ __MODULE__
|> where([u], u.last_active_at >= ^active_after) |> where([u], u.last_active_at >= ^active_after)

View file

@ -46,6 +46,7 @@ defmodule Pleroma.User.Query do
unconfirmed: boolean(), unconfirmed: boolean(),
is_admin: boolean(), is_admin: boolean(),
is_moderator: boolean(), is_moderator: boolean(),
is_suggested: boolean(),
super_users: boolean(), super_users: boolean(),
invisible: boolean(), invisible: boolean(),
internal: boolean(), internal: boolean(),
@ -167,6 +168,10 @@ defp compose_query({:unconfirmed, _}, query) do
where(query, [u], u.is_confirmed == false) where(query, [u], u.is_confirmed == false)
end end
defp compose_query({:is_suggested, bool}, query) do
where(query, [u], u.is_suggested == ^bool)
end
defp compose_query({:followers, %User{id: id}}, query) do defp compose_query({:followers, %User{id: id}}, query) do
query query
|> where([u], u.id != ^id) |> where([u], u.id != ^id)

View file

@ -68,12 +68,14 @@ def fix_media_type(data) do
end end
end end
defp handle_href(href, mediaType) do defp handle_href(href, mediaType, data) do
[ [
%{ %{
"href" => href, "href" => href,
"type" => "Link", "type" => "Link",
"mediaType" => mediaType "mediaType" => mediaType,
"width" => data["width"],
"height" => data["height"]
} }
] ]
end end
@ -81,10 +83,10 @@ defp handle_href(href, mediaType) do
defp fix_url(data) do defp fix_url(data) do
cond do cond do
is_binary(data["url"]) -> is_binary(data["url"]) ->
Map.put(data, "url", handle_href(data["url"], data["mediaType"])) Map.put(data, "url", handle_href(data["url"], data["mediaType"], data))
is_binary(data["href"]) and data["url"] == nil -> is_binary(data["href"]) and data["url"] == nil ->
Map.put(data, "url", handle_href(data["href"], data["mediaType"])) Map.put(data, "url", handle_href(data["href"], data["mediaType"], data))
true -> true ->
data data

View file

@ -63,18 +63,17 @@ def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = pa
date: date date: date
}) })
with {:ok, %{status: code}} when code in 200..299 <- with {:ok, %{status: code}} = result when code in 200..299 <-
result = HTTP.post(
HTTP.post( inbox,
inbox, json,
json, [
[ {"Content-Type", "application/activity+json"},
{"Content-Type", "application/activity+json"}, {"Date", date},
{"Date", date}, {"signature", signature},
{"signature", signature}, {"digest", digest}
{"digest", digest} ]
] ) do
) do
if not Map.has_key?(params, :unreachable_since) || params[:unreachable_since] do if not Map.has_key?(params, :unreachable_since) || params[:unreachable_since] do
Instances.set_reachable(inbox) Instances.set_reachable(inbox)
end end

View file

@ -35,7 +35,9 @@ defmodule Pleroma.Web.AdminAPI.UserController do
:toggle_activation, :toggle_activation,
:activate, :activate,
:deactivate, :deactivate,
:approve :approve,
:suggest,
:unsuggest
] ]
) )
@ -239,6 +241,32 @@ def approve(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = c
render(conn, "index.json", users: updated_users) render(conn, "index.json", users: updated_users)
end end
def suggest(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
{:ok, updated_users} = User.set_suggestion(users, true)
ModerationLog.insert_log(%{
actor: admin,
subject: users,
action: "add_suggestion"
})
render(conn, "index.json", users: updated_users)
end
def unsuggest(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
{:ok, updated_users} = User.set_suggestion(users, false)
ModerationLog.insert_log(%{
actor: admin,
subject: users,
action: "remove_suggestion"
})
render(conn, "index.json", users: updated_users)
end
def index(conn, params) do def index(conn, params) do
{page, page_size} = page_params(params) {page, page_size} = page_params(params)
filters = maybe_parse_filters(params[:filters]) filters = maybe_parse_filters(params[:filters])

View file

@ -80,6 +80,7 @@ def render("show.json", %{user: user}) do
"tags" => user.tags || [], "tags" => user.tags || [],
"is_confirmed" => user.is_confirmed, "is_confirmed" => user.is_confirmed,
"is_approved" => user.is_approved, "is_approved" => user.is_approved,
"is_suggested" => user.is_suggested,
"url" => user.uri || user.ap_id, "url" => user.uri || user.ap_id,
"registration_reason" => user.registration_reason, "registration_reason" => user.registration_reason,
"actor_type" => user.actor_type, "actor_type" => user.actor_type,

View file

@ -216,7 +216,71 @@ def approve_operation do
request_body( request_body(
"Parameters", "Parameters",
%Schema{ %Schema{
description: "POST body for deleting multiple users", description: "POST body for approving multiple users",
type: :object,
properties: %{
nicknames: %Schema{
type: :array,
items: %Schema{type: :string}
}
}
}
),
responses: %{
200 =>
Operation.response("Response", "application/json", %Schema{
type: :object,
properties: %{user: %Schema{type: :array, items: user()}}
}),
403 => Operation.response("Forbidden", "application/json", ApiError)
}
}
end
def suggest_operation do
%Operation{
tags: ["User administration"],
summary: "Suggest multiple users",
operationId: "AdminAPI.UserController.suggest",
security: [%{"oAuth" => ["admin:write:accounts"]}],
parameters: admin_api_params(),
requestBody:
request_body(
"Parameters",
%Schema{
description: "POST body for adding multiple suggested users",
type: :object,
properties: %{
nicknames: %Schema{
type: :array,
items: %Schema{type: :string}
}
}
}
),
responses: %{
200 =>
Operation.response("Response", "application/json", %Schema{
type: :object,
properties: %{user: %Schema{type: :array, items: user()}}
}),
403 => Operation.response("Forbidden", "application/json", ApiError)
}
}
end
def unsuggest_operation do
%Operation{
tags: ["User administration"],
summary: "Unsuggest multiple users",
operationId: "AdminAPI.UserController.unsuggest",
security: [%{"oAuth" => ["admin:write:accounts"]}],
parameters: admin_api_params(),
requestBody:
request_body(
"Parameters",
%Schema{
description: "POST body for removing multiple suggested users",
type: :object, type: :object,
properties: %{ properties: %{
nicknames: %Schema{ nicknames: %Schema{

View file

@ -191,6 +191,7 @@ def delete_account_operation do
parameters: [ parameters: [
Operation.parameter(:password, :query, :string, "Password") Operation.parameter(:password, :query, :string, "Password")
], ],
requestBody: request_body("Parameters", delete_account_request(), required: false),
responses: %{ responses: %{
200 => 200 =>
Operation.response("Success", "application/json", %Schema{ Operation.response("Success", "application/json", %Schema{
@ -237,4 +238,22 @@ def remote_subscribe_operation do
responses: %{200 => Operation.response("Web Page", "test/html", %Schema{type: :string})} responses: %{200 => Operation.response("Web Page", "test/html", %Schema{type: :string})}
} }
end end
defp delete_account_request do
%Schema{
title: "AccountDeleteRequest",
description: "POST body for deleting one's own account",
type: :object,
properties: %{
password: %Schema{
type: :string,
description: "The user's own password for confirmation.",
format: :password
}
},
example: %{
"password" => "prettyp0ony1313"
}
}
end
end end

View file

@ -17,6 +17,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
require Logger require Logger
@search_limit 40
plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(Pleroma.Web.ApiSpec.CastAndValidate)
# Note: Mastodon doesn't allow unauthenticated access (requires read:accounts / read:search) # Note: Mastodon doesn't allow unauthenticated access (requires read:accounts / read:search)
@ -77,7 +79,7 @@ defp search_options(params, user) do
[ [
resolve: params[:resolve], resolve: params[:resolve],
following: params[:following], following: params[:following],
limit: params[:limit], limit: min(params[:limit], @search_limit),
offset: params[:offset], offset: params[:offset],
type: params[:type], type: params[:type],
author: get_author(params), author: get_author(params),

View file

@ -4,11 +4,16 @@
defmodule Pleroma.Web.MastodonAPI.SuggestionController do defmodule Pleroma.Web.MastodonAPI.SuggestionController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
import Ecto.Query
alias Pleroma.FollowingRelationship
alias Pleroma.User
alias Pleroma.UserRelationship
require Logger require Logger
plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["read"]} when action == :index) plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["read"]} when action in [:index, :index2])
plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["write"]} when action in [:dismiss])
def open_api_operation(action) do def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation") operation = String.to_existing_atom("#{action}_operation")
@ -26,7 +31,90 @@ def index_operation do
} }
end end
def index2_operation do
%OpenApiSpex.Operation{
tags: ["Suggestions"],
summary: "Follow suggestions",
operationId: "SuggestionController.index2",
responses: %{
200 => Pleroma.Web.ApiSpec.Helpers.empty_array_response()
}
}
end
def dismiss_operation do
%OpenApiSpex.Operation{
tags: ["Suggestions"],
summary: "Remove a suggestion",
operationId: "SuggestionController.dismiss",
parameters: [
OpenApiSpex.Operation.parameter(
:account_id,
:path,
%OpenApiSpex.Schema{type: :string},
"Account to dismiss",
required: true
)
],
responses: %{
200 => Pleroma.Web.ApiSpec.Helpers.empty_object_response()
}
}
end
@doc "GET /api/v1/suggestions" @doc "GET /api/v1/suggestions"
def index(conn, params), def index(conn, params),
do: Pleroma.Web.MastodonAPI.MastodonAPIController.empty_array(conn, params) do: Pleroma.Web.MastodonAPI.MastodonAPIController.empty_array(conn, params)
@doc "GET /api/v2/suggestions"
def index2(%{assigns: %{user: user}} = conn, params) do
limit = Map.get(params, :limit, 40) |> min(80)
users =
%{is_suggested: true, invisible: false, limit: limit}
|> User.Query.build()
|> exclude_user(user)
|> exclude_relationships(user, [:block, :mute, :suggestion_dismiss])
|> exclude_following(user)
|> Pleroma.Repo.all()
render(conn, "index.json", %{
users: users,
source: :staff,
for: user,
skip_visibility_check: true
})
end
defp exclude_user(query, %User{id: user_id}) do
where(query, [u], u.id != ^user_id)
end
defp exclude_relationships(query, %User{id: user_id}, relationship_types) do
query
|> join(:left, [u], r in UserRelationship,
as: :user_relationships,
on:
r.target_id == u.id and r.source_id == ^user_id and
r.relationship_type in ^relationship_types
)
|> where([user_relationships: r], is_nil(r.target_id))
end
defp exclude_following(query, %User{id: user_id}) do
query
|> join(:left, [u], r in FollowingRelationship,
as: :following_relationships,
on: r.following_id == u.id and r.follower_id == ^user_id and r.state == :follow_accept
)
|> where([following_relationships: r], is_nil(r.following_id))
end
@doc "DELETE /api/v1/suggestions/:account_id"
def dismiss(%{assigns: %{user: source}} = conn, %{account_id: user_id}) do
with %User{} = target <- User.get_cached_by_id(user_id),
{:ok, _} <- UserRelationship.create(:suggestion_dismiss, source, target) do
json(conn, %{})
end
end
end end

View file

@ -269,6 +269,7 @@ defp do_render("show.json", %{user: user} = opts) do
ap_id: user.ap_id, ap_id: user.ap_id,
also_known_as: user.also_known_as, also_known_as: user.also_known_as,
is_confirmed: user.is_confirmed, is_confirmed: user.is_confirmed,
is_suggested: user.is_suggested,
tags: user.tags, tags: user.tags,
hide_followers_count: user.hide_followers_count, hide_followers_count: user.hide_followers_count,
hide_follows_count: user.hide_follows_count, hide_follows_count: user.hide_follows_count,

View file

@ -59,6 +59,7 @@ def features do
"mastodon_api", "mastodon_api",
"mastodon_api_streaming", "mastodon_api_streaming",
"polls", "polls",
"v2_suggestions",
"pleroma_explicit_addressing", "pleroma_explicit_addressing",
"shareable_emoji_packs", "shareable_emoji_packs",
"multifetch", "multifetch",

View file

@ -0,0 +1,28 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.SuggestionView do
use Pleroma.Web, :view
alias Pleroma.Web.MastodonAPI.AccountView
@source_types [:staff, :global, :past_interactions]
def render("index.json", %{users: users} = opts) do
Enum.map(users, fn user ->
opts =
opts
|> Map.put(:user, user)
|> Map.delete(:users)
render("show.json", opts)
end)
end
def render("show.json", %{source: source, user: _user} = opts) when source in @source_types do
%{
source: source,
account: AccountView.render("show.json", opts)
}
end
end

View file

@ -35,7 +35,9 @@ def get_nodeinfo("2.0") do
openRegistrations: Config.get([:instance, :registrations_open]), openRegistrations: Config.get([:instance, :registrations_open]),
usage: %{ usage: %{
users: %{ users: %{
total: Map.get(stats, :user_count, 0) total: Map.get(stats, :user_count, 0),
activeMonth: Pleroma.User.active_user_count(30),
activeHalfyear: Pleroma.User.active_user_count(180)
}, },
localPosts: Map.get(stats, :status_count, 0) localPosts: Map.get(stats, :status_count, 0)
}, },

View file

@ -192,6 +192,9 @@ defmodule Pleroma.Web.Router do
patch("/users/deactivate", UserController, :deactivate) patch("/users/deactivate", UserController, :deactivate)
patch("/users/approve", UserController, :approve) patch("/users/approve", UserController, :approve)
patch("/users/suggest", UserController, :suggest)
patch("/users/unsuggest", UserController, :unsuggest)
get("/relay", RelayController, :index) get("/relay", RelayController, :index)
post("/relay", RelayController, :follow) post("/relay", RelayController, :follow)
delete("/relay", RelayController, :unfollow) delete("/relay", RelayController, :unfollow)
@ -535,6 +538,7 @@ defmodule Pleroma.Web.Router do
delete("/push/subscription", SubscriptionController, :delete) delete("/push/subscription", SubscriptionController, :delete)
get("/suggestions", SuggestionController, :index) get("/suggestions", SuggestionController, :index)
delete("/suggestions/:account_id", SuggestionController, :dismiss)
get("/timelines/home", TimelineController, :home) get("/timelines/home", TimelineController, :home)
get("/timelines/direct", TimelineController, :direct) get("/timelines/direct", TimelineController, :direct)
@ -586,6 +590,8 @@ defmodule Pleroma.Web.Router do
get("/search", SearchController, :search2) get("/search", SearchController, :search2)
post("/media", MediaController, :create2) post("/media", MediaController, :create2)
get("/suggestions", SuggestionController, :index2)
end end
scope "/api", Pleroma.Web do scope "/api", Pleroma.Web do
@ -742,6 +748,12 @@ defmodule Pleroma.Web.Router do
get("/manifest.json", ManifestController, :show) get("/manifest.json", ManifestController, :show)
end end
scope "/", Pleroma.Web do
pipe_through(:pleroma_html)
post("/auth/password", TwitterAPI.PasswordController, :request)
end
scope "/proxy/", Pleroma.Web do scope "/proxy/", Pleroma.Web do
get("/preview/:sig/:url", MediaProxy.MediaProxyController, :preview) get("/preview/:sig/:url", MediaProxy.MediaProxyController, :preview)
get("/preview/:sig/:url/:filename", MediaProxy.MediaProxyController, :preview) get("/preview/:sig/:url/:filename", MediaProxy.MediaProxyController, :preview)

View file

@ -11,9 +11,23 @@ defmodule Pleroma.Web.TwitterAPI.PasswordController do
require Logger require Logger
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
alias Pleroma.PasswordResetToken alias Pleroma.PasswordResetToken
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.TwitterAPI.TwitterAPI
plug(Pleroma.Web.Plugs.RateLimiter, [name: :request] when action == :request)
@doc "POST /auth/password"
def request(conn, params) do
nickname_or_email = params["email"] || params["nickname"]
TwitterAPI.password_reset(nickname_or_email)
json_response(conn, :no_content, "")
end
def reset(conn, %{"token" => token}) do def reset(conn, %{"token" => token}) do
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}), with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),

View file

@ -123,8 +123,10 @@ def change_email(%{assigns: %{user: user}, body_params: body_params} = conn, %{}
end end
end end
def delete_account(%{assigns: %{user: user}} = conn, params) do def delete_account(%{assigns: %{user: user}, body_params: body_params} = conn, params) do
password = params[:password] || "" # This endpoint can accept a query param or JSON body for backwards-compatibility.
# Submitting a JSON body is recommended, so passwords don't end up in server logs.
password = body_params[:password] || params[:password] || ""
case CommonAPI.Utils.confirm_current_password(user, password) do case CommonAPI.Utils.confirm_current_password(user, password) do
{:ok, user} -> {:ok, user} ->

View file

@ -86,7 +86,7 @@ def application do
end end
# Specifies which paths to compile per environment. # Specifies which paths to compile per environment.
defp elixirc_paths(:benchmark), do: ["lib", "benchmarks"] defp elixirc_paths(:benchmark), do: ["lib", "benchmarks", "priv/scrubbers"]
defp elixirc_paths(:test), do: ["lib", "test/support"] defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"] defp elixirc_paths(_), do: ["lib"]
@ -208,7 +208,7 @@ defp deps do
{:mock, "~> 0.3.5", only: :test}, {:mock, "~> 0.3.5", only: :test},
# temporary downgrade for excoveralls, hackney until hackney max_connections bug will be fixed # temporary downgrade for excoveralls, hackney until hackney max_connections bug will be fixed
{:excoveralls, "0.12.3", only: :test}, {:excoveralls, "0.12.3", only: :test},
{:hackney, "~> 1.17.0", override: true}, {:hackney, "~> 1.18.0", override: true},
{:mox, "~> 1.0", only: :test}, {:mox, "~> 1.0", only: :test},
{:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test} {:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test}
] ++ oauth_deps() ] ++ oauth_deps()

View file

@ -11,7 +11,7 @@
"calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"}, "calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"},
"captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "e0f16822d578866e186a0974d65ad58cddc1e2ab", [ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"]}, "captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "e0f16822d578866e186a0974d65ad58cddc1e2ab", [ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"]},
"castore": {:hex, :castore, "0.1.10", "b01a007416a0ae4188e70b3b306236021b16c11474038ead7aff79dd75538c23", [:mix], [], "hexpm", "a48314e0cb45682db2ea27b8ebfa11bd6fa0a6e21a65e5772ad83ca136ff2665"}, "castore": {:hex, :castore, "0.1.10", "b01a007416a0ae4188e70b3b306236021b16c11474038ead7aff79dd75538c23", [:mix], [], "hexpm", "a48314e0cb45682db2ea27b8ebfa11bd6fa0a6e21a65e5772ad83ca136ff2665"},
"certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"}, "certifi": {:hex, :certifi, "2.8.0", "d4fb0a6bb20b7c9c3643e22507e42f356ac090a1dcea9ab99e27e0376d695eba", [:rebar3], [], "hexpm", "6ac7efc1c6f8600b08d625292d4bbf584e14847ce1b6b5c44d983d273e1097ea"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "5.3.2", "5c2f893d05c56ae3f5e24c1b983c2d5dfb88c6d979c9287a76a7feb1e1d8d646", [:mix], [], "hexpm", "d0993402844c49539aeadb3fe46a3c9bd190f1ecf86b6f9ebd71957534c95f04"}, "comeonin": {:hex, :comeonin, "5.3.2", "5c2f893d05c56ae3f5e24c1b983c2d5dfb88c6d979c9287a76a7feb1e1d8d646", [:mix], [], "hexpm", "d0993402844c49539aeadb3fe46a3c9bd190f1ecf86b6f9ebd71957534c95f04"},
"concurrent_limiter": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git", "d81be41024569330f296fc472e24198d7499ba78", [ref: "d81be41024569330f296fc472e24198d7499ba78"]}, "concurrent_limiter": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git", "d81be41024569330f296fc472e24198d7499ba78", [ref: "d81be41024569330f296fc472e24198d7499ba78"]},
@ -55,7 +55,7 @@
"gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"}, "gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"},
"gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"}, "gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"},
"gun": {:hex, :gun, "2.0.0-rc.2", "7c489a32dedccb77b6e82d1f3c5a7dadfbfa004ec14e322cdb5e579c438632d2", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "6b9d1eae146410d727140dbf8b404b9631302ecc2066d1d12f22097ad7d254fc"}, "gun": {:hex, :gun, "2.0.0-rc.2", "7c489a32dedccb77b6e82d1f3c5a7dadfbfa004ec14e322cdb5e579c438632d2", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "6b9d1eae146410d727140dbf8b404b9631302ecc2066d1d12f22097ad7d254fc"},
"hackney": {:hex, :hackney, "1.17.4", "99da4674592504d3fb0cfef0db84c3ba02b4508bae2dff8c0108baa0d6e0977c", [:rebar3], [{:certifi, "~>2.6.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "de16ff4996556c8548d512f4dbe22dd58a587bf3332e7fd362430a7ef3986b16"}, "hackney": {:hex, :hackney, "1.18.0", "c4443d960bb9fba6d01161d01cd81173089686717d9490e5d3606644c48d121f", [:rebar3], [{:certifi, "~>2.8.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "9afcda620704d720db8c6a3123e9848d09c87586dc1c10479c42627b905b5c5e"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"}, "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
"http_signatures": {:hex, :http_signatures, "0.1.1", "ca7ebc1b61542b163644c8c3b1f0e0f41037d35f2395940d3c6c7deceab41fd8", [:mix], [], "hexpm", "cc3b8a007322cc7b624c0c15eec49ee58ac977254ff529a3c482f681465942a3"}, "http_signatures": {:hex, :http_signatures, "0.1.1", "ca7ebc1b61542b163644c8c3b1f0e0f41037d35f2395940d3c6c7deceab41fd8", [:mix], [], "hexpm", "cc3b8a007322cc7b624c0c15eec49ee58ac977254ff529a3c482f681465942a3"},

View file

@ -0,0 +1,11 @@
defmodule Pleroma.Repo.Migrations.ForcePinnedObjectsToExist do
use Ecto.Migration
def change do
execute("UPDATE users SET pinned_objects = '{}' WHERE pinned_objects IS NULL")
alter table("users") do
modify(:pinned_objects, :map, null: false, default: %{})
end
end
end

View file

@ -0,0 +1,11 @@
defmodule Pleroma.Repo.Migrations.AddSuggestions do
use Ecto.Migration
def change do
alter table(:users) do
add(:is_suggested, :boolean, default: false, null: false)
end
create_if_not_exists(index(:users, [:is_suggested]))
end
end

View file

@ -34,4 +34,14 @@ test "it returns internal users when enabled" do
assert %{internal: true} |> Query.build() |> Repo.aggregate(:count) == 2 assert %{internal: true} |> Query.build() |> Repo.aggregate(:count) == 2
end end
end end
test "is_suggested param" do
_user1 = insert(:user, is_suggested: false)
user2 = insert(:user, is_suggested: true)
assert [^user2] =
%{is_suggested: true}
|> User.Query.build()
|> Repo.all()
end
end end

View file

@ -1718,6 +1718,38 @@ test "delete/1 purges a remote user" do
assert user.banner == %{} assert user.banner == %{}
end end
describe "set_suggestion" do
test "suggests a user" do
user = insert(:user, is_suggested: false)
refute user.is_suggested
{:ok, user} = User.set_suggestion(user, true)
assert user.is_suggested
end
test "suggests a list of users" do
unsuggested_users = [
insert(:user, is_suggested: false),
insert(:user, is_suggested: false),
insert(:user, is_suggested: false)
]
{:ok, users} = User.set_suggestion(unsuggested_users, true)
assert Enum.count(users) == 3
Enum.each(users, fn user ->
assert user.is_suggested
end)
end
test "unsuggests a user" do
user = insert(:user, is_suggested: true)
assert user.is_suggested
{:ok, user} = User.set_suggestion(user, false)
refute user.is_suggested
end
end
test "get_public_key_for_ap_id fetches a user that's not in the db" do test "get_public_key_for_ap_id fetches a user that's not in the db" do
assert {:ok, _key} = User.get_public_key_for_ap_id("http://mastodon.example.org/users/admin") assert {:ok, _key} = User.get_public_key_for_ap_id("http://mastodon.example.org/users/admin")
end end
@ -2410,13 +2442,16 @@ test "update_last_active_at/1" do
test "active_user_count/1" do test "active_user_count/1" do
insert(:user) insert(:user)
insert(:user, %{local: false}) insert(:user, %{local: false})
insert(:user, %{last_active_at: Timex.shift(NaiveDateTime.utc_now(), weeks: -5)})
insert(:user, %{last_active_at: Timex.shift(NaiveDateTime.utc_now(), weeks: -3)})
insert(:user, %{last_active_at: NaiveDateTime.utc_now()}) insert(:user, %{last_active_at: NaiveDateTime.utc_now()})
insert(:user, %{last_active_at: Timex.shift(NaiveDateTime.utc_now(), days: -15)})
insert(:user, %{last_active_at: Timex.shift(NaiveDateTime.utc_now(), weeks: -6)})
insert(:user, %{last_active_at: Timex.shift(NaiveDateTime.utc_now(), months: -7)})
insert(:user, %{last_active_at: Timex.shift(NaiveDateTime.utc_now(), years: -2)})
assert User.active_user_count() == 2 assert User.active_user_count() == 2
assert User.active_user_count(6) == 3 assert User.active_user_count(180) == 3
assert User.active_user_count(1) == 1 assert User.active_user_count(365) == 4
assert User.active_user_count(1000) == 5
end end
describe "pins" do describe "pins" do

View file

@ -776,6 +776,20 @@ test "doesn't return blocked activities" do
assert Enum.member?(activities, activity_one) assert Enum.member?(activities, activity_one)
end end
test "doesn't return activities from deactivated users" do
_user = insert(:user)
deactivated = insert(:user)
active = insert(:user)
{:ok, activity_one} = CommonAPI.post(deactivated, %{status: "hey!"})
{:ok, activity_two} = CommonAPI.post(active, %{status: "yay!"})
{:ok, _updated_user} = User.set_activation(deactivated, false)
activities = ActivityPub.fetch_activities([], %{})
refute Enum.member?(activities, activity_one)
assert Enum.member?(activities, activity_two)
end
test "always see your own posts even when they address people you block" do test "always see your own posts even when they address people you block" do
user = insert(:user) user = insert(:user)
blockee = insert(:user) blockee = insert(:user)

View file

@ -105,5 +105,37 @@ test "it handles image dimensions" do
assert attachment.mediaType == "image/jpeg" assert attachment.mediaType == "image/jpeg"
end end
test "it transforms image dimentions to our internal format" do
attachment = %{
"type" => "Document",
"name" => "Hello world",
"url" => "https://media.example.tld/1.jpg",
"width" => 880,
"height" => 960,
"mediaType" => "image/jpeg",
"blurhash" => "eTKL26+HDjcEIBVl;ds+K6t301W.t7nit7y1E,R:v}ai4nXSt7V@of"
}
expected = %AttachmentValidator{
type: "Document",
name: "Hello world",
mediaType: "image/jpeg",
blurhash: "eTKL26+HDjcEIBVl;ds+K6t301W.t7nit7y1E,R:v}ai4nXSt7V@of",
url: [
%AttachmentValidator.UrlObjectValidator{
type: "Link",
mediaType: "image/jpeg",
href: "https://media.example.tld/1.jpg",
width: 880,
height: 960
}
]
}
{:ok, ^expected} =
AttachmentValidator.cast_and_validate(attachment)
|> Ecto.Changeset.apply_action(:insert)
end
end end
end end

View file

@ -58,7 +58,8 @@ test "it remaps video URLs as attachments if necessary" do
"href" => "href" =>
"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4", "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
"mediaType" => "video/mp4", "mediaType" => "video/mp4",
"type" => "Link" "type" => "Link",
"width" => 480
} }
] ]
} }
@ -79,7 +80,8 @@ test "it remaps video URLs as attachments if necessary" do
"href" => "href" =>
"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4", "https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4",
"mediaType" => "video/mp4", "mediaType" => "video/mp4",
"type" => "Link" "type" => "Link",
"height" => 1080
} }
] ]
} }
@ -107,7 +109,8 @@ test "it works for peertube videos with only their mpegURL map" do
"href" => "href" =>
"https://peertube.stream/static/streaming-playlists/hls/abece3c3-b9c6-47f4-8040-f3eed8c602e6/abece3c3-b9c6-47f4-8040-f3eed8c602e6-1080-fragmented.mp4", "https://peertube.stream/static/streaming-playlists/hls/abece3c3-b9c6-47f4-8040-f3eed8c602e6/abece3c3-b9c6-47f4-8040-f3eed8c602e6-1080-fragmented.mp4",
"mediaType" => "video/mp4", "mediaType" => "video/mp4",
"type" => "Link" "type" => "Link",
"height" => 1080
} }
] ]
} }

View file

@ -524,4 +524,44 @@ test "returns {:ok, %Object{}} for success case" do
) )
end end
end end
describe "fix_attachments/1" do
test "puts dimensions into attachment url field" do
object = %{
"attachment" => [
%{
"type" => "Document",
"name" => "Hello world",
"url" => "https://media.example.tld/1.jpg",
"width" => 880,
"height" => 960,
"mediaType" => "image/jpeg",
"blurhash" => "eTKL26+HDjcEIBVl;ds+K6t301W.t7nit7y1E,R:v}ai4nXSt7V@of"
}
]
}
expected = %{
"attachment" => [
%{
"type" => "Document",
"name" => "Hello world",
"url" => [
%{
"type" => "Link",
"mediaType" => "image/jpeg",
"href" => "https://media.example.tld/1.jpg",
"width" => 880,
"height" => 960
}
],
"mediaType" => "image/jpeg",
"blurhash" => "eTKL26+HDjcEIBVl;ds+K6t301W.t7nit7y1E,R:v}ai4nXSt7V@of"
}
]
}
assert Transmogrifier.fix_attachments(object) == expected
end
end
end end

View file

@ -873,6 +873,56 @@ test "PATCH /api/pleroma/admin/users/approve", %{admin: admin, conn: conn} do
"@#{admin.nickname} approved users: @#{user_one.nickname}, @#{user_two.nickname}" "@#{admin.nickname} approved users: @#{user_one.nickname}, @#{user_two.nickname}"
end end
test "PATCH /api/pleroma/admin/users/suggest", %{admin: admin, conn: conn} do
user1 = insert(:user, is_suggested: false)
user2 = insert(:user, is_suggested: false)
response =
conn
|> put_req_header("content-type", "application/json")
|> patch(
"/api/pleroma/admin/users/suggest",
%{nicknames: [user1.nickname, user2.nickname]}
)
|> json_response_and_validate_schema(200)
assert Enum.map(response["users"], & &1["is_suggested"]) == [true, true]
[user1, user2] = Repo.reload!([user1, user2])
assert user1.is_suggested
assert user2.is_suggested
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} added suggested users: @#{user1.nickname}, @#{user2.nickname}"
end
test "PATCH /api/pleroma/admin/users/unsuggest", %{admin: admin, conn: conn} do
user1 = insert(:user, is_suggested: true)
user2 = insert(:user, is_suggested: true)
response =
conn
|> put_req_header("content-type", "application/json")
|> patch(
"/api/pleroma/admin/users/unsuggest",
%{nicknames: [user1.nickname, user2.nickname]}
)
|> json_response_and_validate_schema(200)
assert Enum.map(response["users"], & &1["is_suggested"]) == [false, false]
[user1, user2] = Repo.reload!([user1, user2])
refute user1.is_suggested
refute user2.is_suggested
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} removed suggested users: @#{user1.nickname}, @#{user2.nickname}"
end
test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admin, conn: conn} do test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admin, conn: conn} do
user = insert(:user) user = insert(:user)
@ -906,6 +956,7 @@ defp user_response(user, attrs \\ %{}) do
"display_name" => HTML.strip_tags(user.name || user.nickname), "display_name" => HTML.strip_tags(user.name || user.nickname),
"is_confirmed" => true, "is_confirmed" => true,
"is_approved" => true, "is_approved" => true,
"is_suggested" => false,
"url" => user.ap_id, "url" => user.ap_id,
"registration_reason" => nil, "registration_reason" => nil,
"actor_type" => "Person", "actor_type" => "Person",

View file

@ -4,8 +4,11 @@
defmodule Pleroma.Web.MastodonAPI.SuggestionControllerTest do defmodule Pleroma.Web.MastodonAPI.SuggestionControllerTest do
use Pleroma.Web.ConnCase, async: true use Pleroma.Web.ConnCase, async: true
alias Pleroma.UserRelationship
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
setup do: oauth_access(["read"]) setup do: oauth_access(["read", "write"])
test "returns empty result", %{conn: conn} do test "returns empty result", %{conn: conn} do
res = res =
@ -15,4 +18,66 @@ test "returns empty result", %{conn: conn} do
assert res == [] assert res == []
end end
test "returns v2 suggestions", %{conn: conn} do
%{id: user_id} = insert(:user, is_suggested: true)
res =
conn
|> get("/api/v2/suggestions")
|> json_response_and_validate_schema(200)
assert [%{"source" => "staff", "account" => %{"id" => ^user_id}}] = res
end
test "returns v2 suggestions excluding dismissed accounts", %{conn: conn} do
%{id: user_id} = insert(:user, is_suggested: true)
conn
|> delete("/api/v1/suggestions/#{user_id}")
|> json_response_and_validate_schema(200)
res =
conn
|> get("/api/v2/suggestions")
|> json_response_and_validate_schema(200)
assert [] = res
end
test "returns v2 suggestions excluding blocked accounts", %{conn: conn, user: blocker} do
blocked = insert(:user, is_suggested: true)
{:ok, _} = CommonAPI.block(blocker, blocked)
res =
conn
|> get("/api/v2/suggestions")
|> json_response_and_validate_schema(200)
assert [] = res
end
test "returns v2 suggestions excluding followed accounts", %{conn: conn, user: follower} do
followed = insert(:user, is_suggested: true)
{:ok, _, _, _} = CommonAPI.follow(follower, followed)
res =
conn
|> get("/api/v2/suggestions")
|> json_response_and_validate_schema(200)
assert [] = res
end
test "dismiss suggestion", %{conn: conn, user: source} do
target = insert(:user, is_suggested: true)
res =
conn
|> delete("/api/v1/suggestions/#{target.id}")
|> json_response_and_validate_schema(200)
assert res == %{}
assert UserRelationship.exists?(:suggestion_dismiss, source, target)
end
end end

View file

@ -83,6 +83,7 @@ test "Represent a user account" do
tags: [], tags: [],
is_admin: false, is_admin: false,
is_moderator: false, is_moderator: false,
is_suggested: false,
hide_favorites: true, hide_favorites: true,
hide_followers: false, hide_followers: false,
hide_follows: false, hide_follows: false,
@ -183,6 +184,7 @@ test "Represent a Service(bot) account" do
tags: [], tags: [],
is_admin: false, is_admin: false,
is_moderator: false, is_moderator: false,
is_suggested: false,
hide_favorites: true, hide_favorites: true,
hide_followers: false, hide_followers: false,
hide_follows: false, hide_follows: false,

View file

@ -0,0 +1,34 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.SuggestionViewTest do
use Pleroma.DataCase, async: true
import Pleroma.Factory
alias Pleroma.Web.MastodonAPI.SuggestionView, as: View
test "show.json" do
user = insert(:user, is_suggested: true)
json = View.render("show.json", %{user: user, source: :staff, skip_visibility_check: true})
assert json.source == :staff
assert json.account.id == user.id
end
test "index.json" do
user1 = insert(:user, is_suggested: true)
user2 = insert(:user, is_suggested: true)
user3 = insert(:user, is_suggested: true)
[suggestion1, suggestion2, suggestion3] =
View.render("index.json", %{
users: [user1, user2, user3],
source: :staff,
skip_visibility_check: true
})
assert suggestion1.source == :staff
assert suggestion2.account.id == user2.id
assert suggestion3.account.url == user3.ap_id
end
end

View file

@ -95,6 +95,7 @@ test "api routes are detected correctly" do
".well-known", ".well-known",
"nodeinfo", "nodeinfo",
"manifest.json", "manifest.json",
"auth",
"proxy", "proxy",
"test", "test",
"user_exists", "user_exists",

View file

@ -5,10 +5,14 @@
defmodule Pleroma.Web.TwitterAPI.PasswordControllerTest do defmodule Pleroma.Web.TwitterAPI.PasswordControllerTest do
use Pleroma.Web.ConnCase use Pleroma.Web.ConnCase
alias Pleroma.Config
alias Pleroma.PasswordResetToken alias Pleroma.PasswordResetToken
alias Pleroma.Repo
alias Pleroma.Tests.ObanHelpers
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
import Pleroma.Factory import Pleroma.Factory
import Swoosh.TestAssertions
describe "GET /api/pleroma/password_reset/token" do describe "GET /api/pleroma/password_reset/token" do
test "it returns error when token invalid", %{conn: conn} do test "it returns error when token invalid", %{conn: conn} do
@ -116,4 +120,94 @@ test "it sets password_reset_pending to false", %{conn: conn} do
assert User.get_by_id(user.id).password_reset_pending == false assert User.get_by_id(user.id).password_reset_pending == false
end end
end end
describe "POST /auth/password, with valid parameters" do
setup %{conn: conn} do
user = insert(:user)
conn = post(conn, "/auth/password?email=#{user.email}")
%{conn: conn, user: user}
end
test "it returns 204", %{conn: conn} do
assert empty_json_response(conn)
end
test "it creates a PasswordResetToken record for user", %{user: user} do
token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
assert token_record
end
test "it sends an email to user", %{user: user} do
ObanHelpers.perform_all()
token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token)
notify_email = Config.get([:instance, :notify_email])
instance_name = Config.get([:instance, :name])
assert_email_sent(
from: {instance_name, notify_email},
to: {user.name, user.email},
html_body: email.html_body
)
end
end
describe "POST /auth/password, with nickname" do
test "it returns 204", %{conn: conn} do
user = insert(:user)
assert conn
|> post("/auth/password?nickname=#{user.nickname}")
|> empty_json_response()
ObanHelpers.perform_all()
token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token)
notify_email = Config.get([:instance, :notify_email])
instance_name = Config.get([:instance, :name])
assert_email_sent(
from: {instance_name, notify_email},
to: {user.name, user.email},
html_body: email.html_body
)
end
test "it doesn't fail when a user has no email", %{conn: conn} do
user = insert(:user, %{email: nil})
assert conn
|> post("/auth/password?nickname=#{user.nickname}")
|> empty_json_response()
end
end
describe "POST /auth/password, with invalid parameters" do
setup do
user = insert(:user)
{:ok, user: user}
end
test "it returns 204 when user is not found", %{conn: conn, user: user} do
conn = post(conn, "/auth/password?email=nonexisting_#{user.email}")
assert empty_json_response(conn)
end
test "it returns 204 when user is not local", %{conn: conn, user: user} do
{:ok, user} = Repo.update(Ecto.Changeset.change(user, local: false))
conn = post(conn, "/auth/password?email=#{user.email}")
assert empty_json_response(conn)
end
test "it returns 204 when user is deactivated", %{conn: conn, user: user} do
{:ok, user} = Repo.update(Ecto.Changeset.change(user, is_active: false, local: true))
conn = post(conn, "/auth/password?email=#{user.email}")
assert empty_json_response(conn)
end
end
end end

View file

@ -473,7 +473,10 @@ test "without permissions", %{conn: conn} do
test "with proper permissions and wrong or missing password", %{conn: conn} do test "with proper permissions and wrong or missing password", %{conn: conn} do
for params <- [%{"password" => "hi"}, %{}] do for params <- [%{"password" => "hi"}, %{}] do
ret_conn = post(conn, "/api/pleroma/delete_account", params) ret_conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/delete_account", params)
assert json_response_and_validate_schema(ret_conn, 200) == %{ assert json_response_and_validate_schema(ret_conn, 200) == %{
"error" => "Invalid password." "error" => "Invalid password."
@ -481,8 +484,28 @@ test "with proper permissions and wrong or missing password", %{conn: conn} do
end end
end end
test "with proper permissions and valid password", %{conn: conn, user: user} do test "with proper permissions and valid password (URL query)", %{conn: conn, user: user} do
conn = post(conn, "/api/pleroma/delete_account?password=test") conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/delete_account?password=test")
ObanHelpers.perform_all()
assert json_response_and_validate_schema(conn, 200) == %{"status" => "success"}
user = User.get_by_id(user.id)
refute user.is_active
assert user.name == nil
assert user.bio == ""
assert user.password_hash == nil
end
test "with proper permissions and valid password (JSON body)", %{conn: conn, user: user} do
conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/delete_account", %{password: "test"})
ObanHelpers.perform_all() ObanHelpers.perform_all()
assert json_response_and_validate_schema(conn, 200) == %{"status" => "success"} assert json_response_and_validate_schema(conn, 200) == %{"status" => "success"}