Merge branch 'develop' into fix/attachments-cleanup
This commit is contained in:
commit
22e94bb2c7
65 changed files with 738 additions and 277 deletions
|
@ -6,10 +6,6 @@ variables: &global_variables
|
|||
POSTGRES_PASSWORD: postgres
|
||||
DB_HOST: postgres
|
||||
MIX_ENV: test
|
||||
DOCKER_DRIVER: overlay2
|
||||
DOCKER_HOST: unix:///var/run/docker.sock
|
||||
DOCKER_IMAGE: $CI_REGISTRY_IMAGE:latest
|
||||
DOCKER_IMAGE_SHA: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
|
||||
|
||||
cache: &global_cache_policy
|
||||
key: ${CI_COMMIT_REF_SLUG}
|
||||
|
@ -274,16 +270,62 @@ arm64-musl:
|
|||
docker:
|
||||
stage: docker
|
||||
image: docker:latest
|
||||
tags:
|
||||
- dind
|
||||
cache: {}
|
||||
dependencies: []
|
||||
variables: &docker-variables
|
||||
DOCKER_DRIVER: overlay2
|
||||
DOCKER_HOST: unix:///var/run/docker.sock
|
||||
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
|
||||
IMAGE_TAG_SLUG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
|
||||
IMAGE_TAG_LATEST: $CI_REGISTRY_IMAGE:latest
|
||||
IMAGE_TAG_LATEST_STABLE: $CI_REGISTRY_IMAGE:latest-stable
|
||||
before_script: &before-docker
|
||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||
- docker pull $IMAGE_TAG_SLUG || true
|
||||
- export CI_JOB_TIMESTAMP=$(date --utc -Iseconds)
|
||||
- export CI_VCS_REF=$CI_COMMIT_SHORT_SHA
|
||||
allow_failure: true
|
||||
script:
|
||||
- docker pull $DOCKER_IMAGE || true
|
||||
- docker build --cache-from $DOCKER_IMAGE --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $DOCKER_IMAGE_SHA -t $DOCKER_IMAGE .
|
||||
- docker push $DOCKER_IMAGE_SHA
|
||||
- docker push $DOCKER_IMAGE
|
||||
- docker build --cache-from $IMAGE_TAG_SLUG --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $IMAGE_TAG -t $IMAGE_TAG_SLUG -t $IMAGE_TAG_LATEST .
|
||||
- docker push $IMAGE_TAG
|
||||
- docker push $IMAGE_TAG_SLUG
|
||||
- docker push $IMAGE_TAG_LATEST
|
||||
tags:
|
||||
- dind
|
||||
only:
|
||||
- develop
|
||||
- develop@pleroma/pleroma
|
||||
|
||||
docker-stable:
|
||||
stage: docker
|
||||
image: docker:latest
|
||||
cache: {}
|
||||
dependencies: []
|
||||
variables: *docker-variables
|
||||
before_script: *before-docker
|
||||
allow_failure: true
|
||||
script:
|
||||
- docker build --cache-from $IMAGE_TAG_SLUG --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $IMAGE_TAG -t $IMAGE_TAG_SLUG -t $IMAGE_TAG_LATEST_STABLE .
|
||||
- docker push $IMAGE_TAG
|
||||
- docker push $IMAGE_TAG_SLUG
|
||||
- docker push $IMAGE_TAG_LATEST_STABLE
|
||||
tags:
|
||||
- dind
|
||||
only:
|
||||
- stable@pleroma/pleroma
|
||||
|
||||
docker-release:
|
||||
stage: docker
|
||||
image: docker:latest
|
||||
cache: {}
|
||||
dependencies: []
|
||||
variables: *docker-variables
|
||||
before_script: *before-docker
|
||||
allow_failure: true
|
||||
script:
|
||||
- docker build --cache-from $IMAGE_TAG_SLUG --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $IMAGE_TAG -t $IMAGE_TAG_SLUG .
|
||||
- docker push $IMAGE_TAG
|
||||
- docker push $IMAGE_TAG_SLUG
|
||||
tags:
|
||||
- dind
|
||||
only:
|
||||
- /^release/.*$/@pleroma/pleroma
|
||||
|
|
|
@ -10,9 +10,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- **Breaking**: MDII uploader
|
||||
|
||||
### Changed
|
||||
- **Breaking:** Pleroma won't start if it detects unapplied migrations
|
||||
- **Breaking:** attachments are removed along with statuses when there are no other references to it
|
||||
- **Breaking:** Elixir >=1.8 is now required (was >= 1.7)
|
||||
- **Breaking:** attachment links (`config :pleroma, :instance, no_attachment_links` and `config :pleroma, Pleroma.Upload, link_name`) disabled by default
|
||||
- **Breaking:** OAuth: defaulted `[:auth, :enforce_oauth_admin_scope_usage]` setting to `true` which demands `admin` OAuth scope to perform admin actions (in addition to `is_admin` flag on User); make sure to use bundled or newer versions of AdminFE & PleromaFE to access admin / moderator features.
|
||||
- Replaced [pleroma_job_queue](https://git.pleroma.social/pleroma/pleroma_job_queue) and `Pleroma.Web.Federator.RetryQueue` with [Oban](https://github.com/sorentwo/oban) (see [`docs/config.md`](docs/config.md) on migrating customized worker / retry settings)
|
||||
- Introduced [quantum](https://github.com/quantum-elixir/quantum-core) job scheduler
|
||||
- Enabled `:instance, extended_nickname_format` in the default config
|
||||
|
@ -42,6 +44,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Mastodon API, streaming: Add `pleroma.direct_conversation_id` to the `conversation` stream event payload.
|
||||
- Admin API: Render whole status in grouped reports
|
||||
- Mastodon API: User timelines will now respect blocks, unless you are getting the user timeline of somebody you blocked (which would be empty otherwise).
|
||||
- Mastodon API: Favoriting / Repeating a post multiple times will now return the identical response every time. Before, executing that action twice would return an error ("already favorited") on the second try.
|
||||
</details>
|
||||
|
||||
### Added
|
||||
|
@ -90,6 +93,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Mastodon API: `/api/v1/update_credentials` accepts `actor_type` field.
|
||||
- Captcha: Support native provider
|
||||
- Captcha: Enable by default
|
||||
- Mastodon API: Add support for `account_id` param to filter notifications by the account
|
||||
- Mastodon API: Add `emoji_reactions` property to Statuses
|
||||
</details>
|
||||
|
||||
### Fixed
|
||||
|
|
|
@ -9,7 +9,7 @@ def generate_like_activities(user, posts) do
|
|||
{time, _} =
|
||||
:timer.tc(fn ->
|
||||
Task.async_stream(
|
||||
Enum.take_random(posts, count_likes),
|
||||
Enum.take_random(posts, count_likes),
|
||||
fn post -> {:ok, _, _} = CommonAPI.favorite(post.id, user) end,
|
||||
max_concurrency: 10,
|
||||
timeout: 30_000
|
||||
|
@ -142,6 +142,48 @@ defp do_generate_activity(users) do
|
|||
CommonAPI.post(Enum.random(users), post)
|
||||
end
|
||||
|
||||
def generate_power_intervals(opts \\ []) do
|
||||
count = Keyword.get(opts, :count, 20)
|
||||
power = Keyword.get(opts, :power, 2)
|
||||
IO.puts("Generating #{count} intervals for a power #{power} series...")
|
||||
counts = Enum.map(1..count, fn n -> :math.pow(n, power) end)
|
||||
sum = Enum.sum(counts)
|
||||
|
||||
densities =
|
||||
Enum.map(counts, fn c ->
|
||||
c / sum
|
||||
end)
|
||||
|
||||
densities
|
||||
|> Enum.reduce(0, fn density, acc ->
|
||||
if acc == 0 do
|
||||
[{0, density}]
|
||||
else
|
||||
[{_, lower} | _] = acc
|
||||
[{lower, lower + density} | acc]
|
||||
end
|
||||
end)
|
||||
|> Enum.reverse()
|
||||
end
|
||||
|
||||
def generate_tagged_activities(opts \\ []) do
|
||||
tag_count = Keyword.get(opts, :tag_count, 20)
|
||||
users = Keyword.get(opts, :users, Repo.all(User))
|
||||
activity_count = Keyword.get(opts, :count, 200_000)
|
||||
|
||||
intervals = generate_power_intervals(count: tag_count)
|
||||
|
||||
IO.puts(
|
||||
"Generating #{activity_count} activities using #{tag_count} different tags of format `tag_n`, starting at tag_0"
|
||||
)
|
||||
|
||||
Enum.each(1..activity_count, fn _ ->
|
||||
random = :rand.uniform()
|
||||
i = Enum.find_index(intervals, fn {lower, upper} -> lower <= random && upper > random end)
|
||||
CommonAPI.post(Enum.random(users), %{"status" => "a post with the tag #tag_#{i}"})
|
||||
end)
|
||||
end
|
||||
|
||||
defp do_generate_activity_with_mention(user, users) do
|
||||
mentions_cnt = Enum.random([2, 3, 4, 5])
|
||||
with_user = Enum.random([true, false])
|
||||
|
|
87
benchmarks/mix/tasks/pleroma/benchmarks/tags.ex
Normal file
87
benchmarks/mix/tasks/pleroma/benchmarks/tags.ex
Normal file
|
@ -0,0 +1,87 @@
|
|||
defmodule Mix.Tasks.Pleroma.Benchmarks.Tags do
|
||||
use Mix.Task
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.LoadTesting.Generator
|
||||
import Ecto.Query
|
||||
|
||||
def run(_args) do
|
||||
Mix.Pleroma.start_pleroma()
|
||||
activities_count = Repo.aggregate(from(a in Pleroma.Activity), :count, :id)
|
||||
|
||||
if activities_count == 0 do
|
||||
IO.puts("Did not find any activities, cleaning and generating")
|
||||
clean_tables()
|
||||
Generator.generate_users(users_max: 10)
|
||||
Generator.generate_tagged_activities()
|
||||
else
|
||||
IO.puts("Found #{activities_count} activities, won't generate new ones")
|
||||
end
|
||||
|
||||
tags = Enum.map(0..20, fn i -> {"For #tag_#{i}", "tag_#{i}"} end)
|
||||
|
||||
Enum.each(tags, fn {_, tag} ->
|
||||
query =
|
||||
from(o in Pleroma.Object,
|
||||
where: fragment("(?)->'tag' \\? (?)", o.data, ^tag)
|
||||
)
|
||||
|
||||
count = Repo.aggregate(query, :count, :id)
|
||||
IO.puts("Database contains #{count} posts tagged with #{tag}")
|
||||
end)
|
||||
|
||||
user = Repo.all(Pleroma.User) |> List.first()
|
||||
|
||||
Benchee.run(
|
||||
%{
|
||||
"Hashtag fetching, any" => fn tags ->
|
||||
Pleroma.Web.MastodonAPI.TimelineController.hashtag_fetching(
|
||||
%{
|
||||
"any" => tags
|
||||
},
|
||||
user,
|
||||
false
|
||||
)
|
||||
end,
|
||||
# Will always return zero results because no overlapping hashtags are generated.
|
||||
"Hashtag fetching, all" => fn tags ->
|
||||
Pleroma.Web.MastodonAPI.TimelineController.hashtag_fetching(
|
||||
%{
|
||||
"all" => tags
|
||||
},
|
||||
user,
|
||||
false
|
||||
)
|
||||
end
|
||||
},
|
||||
inputs:
|
||||
tags
|
||||
|> Enum.map(fn {_, v} -> v end)
|
||||
|> Enum.chunk_every(2)
|
||||
|> Enum.map(fn tags -> {"For #{inspect(tags)}", tags} end),
|
||||
time: 5
|
||||
)
|
||||
|
||||
Benchee.run(
|
||||
%{
|
||||
"Hashtag fetching" => fn tag ->
|
||||
Pleroma.Web.MastodonAPI.TimelineController.hashtag_fetching(
|
||||
%{
|
||||
"tag" => tag
|
||||
},
|
||||
user,
|
||||
false
|
||||
)
|
||||
end
|
||||
},
|
||||
inputs: tags,
|
||||
time: 5
|
||||
)
|
||||
end
|
||||
|
||||
defp clean_tables do
|
||||
IO.puts("Deleting old data...\n")
|
||||
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE users CASCADE;")
|
||||
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE activities CASCADE;")
|
||||
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE objects CASCADE;")
|
||||
end
|
||||
end
|
|
@ -561,7 +561,7 @@
|
|||
|
||||
config :pleroma,
|
||||
:auth,
|
||||
enforce_oauth_admin_scope_usage: false,
|
||||
enforce_oauth_admin_scope_usage: true,
|
||||
oauth_consumer_strategies: oauth_consumer_strategies
|
||||
|
||||
config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Sendmail, enabled: false
|
||||
|
|
|
@ -29,6 +29,7 @@ Has these additional fields under the `pleroma` object:
|
|||
- `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
|
||||
- `thread_muted`: true if the thread the post belongs to is muted
|
||||
- `emoji_reactions`: An object with all the emoji reactions with count. Contains no information about the reacting users, for that use the `emoji_reactions_by` endpoint.
|
||||
|
||||
## Attachments
|
||||
|
||||
|
|
|
@ -312,9 +312,7 @@ def restrict_deactivated_users(query) do
|
|||
from(u in User.Query.build(deactivated: true), select: u.ap_id)
|
||||
|> Repo.all()
|
||||
|
||||
from(activity in query,
|
||||
where: activity.actor not in ^deactivated_users
|
||||
)
|
||||
Activity.Queries.exclude_authors(query, deactivated_users)
|
||||
end
|
||||
|
||||
defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search
|
||||
|
|
|
@ -12,6 +12,7 @@ defmodule Pleroma.Activity.Queries do
|
|||
@type query :: Ecto.Queryable.t() | Activity.t()
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.User
|
||||
|
||||
@spec by_ap_id(query, String.t()) :: query
|
||||
def by_ap_id(query \\ Activity, ap_id) do
|
||||
|
@ -29,6 +30,11 @@ def by_actor(query \\ Activity, actor) do
|
|||
)
|
||||
end
|
||||
|
||||
@spec by_author(query, String.t()) :: query
|
||||
def by_author(query \\ Activity, %User{ap_id: ap_id}) do
|
||||
from(a in query, where: a.actor == ^ap_id)
|
||||
end
|
||||
|
||||
@spec by_object_id(query, String.t() | [String.t()]) :: query
|
||||
def by_object_id(query \\ Activity, object_id)
|
||||
|
||||
|
@ -72,4 +78,8 @@ def exclude_type(query \\ Activity, activity_type) do
|
|||
where: fragment("(?)->>'type' != ?", activity.data, ^activity_type)
|
||||
)
|
||||
end
|
||||
|
||||
def exclude_authors(query \\ Activity, actors) do
|
||||
from(activity in query, where: activity.actor not in ^actors)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,18 +26,23 @@ def search(user, search_query, options \\ []) do
|
|||
|> query_with(index_type, search_query)
|
||||
|> maybe_restrict_local(user)
|
||||
|> maybe_restrict_author(author)
|
||||
|> maybe_restrict_blocked(user)
|
||||
|> Pagination.fetch_paginated(%{"offset" => offset, "limit" => limit}, :offset)
|
||||
|> maybe_fetch(user, search_query)
|
||||
end
|
||||
|
||||
def maybe_restrict_author(query, %User{} = author) do
|
||||
from([a, o] in query,
|
||||
where: a.actor == ^author.ap_id
|
||||
)
|
||||
Activity.Queries.by_author(query, author)
|
||||
end
|
||||
|
||||
def maybe_restrict_author(query, _), do: query
|
||||
|
||||
def maybe_restrict_blocked(query, %User{} = user) do
|
||||
Activity.Queries.exclude_authors(query, User.blocked_users_ap_ids(user))
|
||||
end
|
||||
|
||||
def maybe_restrict_blocked(query, _), do: query
|
||||
|
||||
defp restrict_public(q) do
|
||||
from([a, o] in q,
|
||||
where: fragment("?->>'type' = 'Create'", a.data),
|
||||
|
|
|
@ -33,6 +33,7 @@ def user_agent do
|
|||
def start(_type, _args) do
|
||||
Pleroma.HTML.compile_scrubbers()
|
||||
Pleroma.Config.DeprecationWarnings.warn()
|
||||
Pleroma.Repo.check_migrations_applied!()
|
||||
setup_instrumenters()
|
||||
load_custom_modules()
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ def call(%{assigns: %{user: %User{is_admin: true}} = assigns} = conn, _) do
|
|||
token && OAuth.Scopes.contains_admin_scopes?(token.scopes) ->
|
||||
# Note: checking for _any_ admin scope presence, not necessarily fitting requested action.
|
||||
# Thus, controller must explicitly invoke OAuthScopesPlug to verify scope requirements.
|
||||
# Admin might opt out of admin scope for some apps to block any admin actions from them.
|
||||
conn
|
||||
|
||||
true ->
|
||||
|
|
|
@ -8,6 +8,8 @@ defmodule Pleroma.Repo do
|
|||
adapter: Ecto.Adapters.Postgres,
|
||||
migration_timestamps: [type: :naive_datetime_usec]
|
||||
|
||||
require Logger
|
||||
|
||||
defmodule Instrumenter do
|
||||
use Prometheus.EctoInstrumenter
|
||||
end
|
||||
|
@ -47,4 +49,37 @@ def get_assoc(resource, association) do
|
|||
_ -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
def check_migrations_applied!() do
|
||||
unless Pleroma.Config.get(
|
||||
[:i_am_aware_this_may_cause_data_loss, :disable_migration_check],
|
||||
false
|
||||
) do
|
||||
Ecto.Migrator.with_repo(__MODULE__, fn repo ->
|
||||
down_migrations =
|
||||
Ecto.Migrator.migrations(repo)
|
||||
|> Enum.reject(fn
|
||||
{:up, _, _} -> true
|
||||
{:down, _, _} -> false
|
||||
end)
|
||||
|
||||
if length(down_migrations) > 0 do
|
||||
down_migrations_text =
|
||||
Enum.map(down_migrations, fn {:down, id, name} -> "- #{name} (#{id})\n" end)
|
||||
|
||||
Logger.error(
|
||||
"The following migrations were not applied:\n#{down_migrations_text}If you want to start Pleroma anyway, set\nconfig :pleroma, :i_am_aware_this_may_cause_data_loss, disable_migration_check: true"
|
||||
)
|
||||
|
||||
raise Pleroma.Repo.UnappliedMigrationsError
|
||||
end
|
||||
end)
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Pleroma.Repo.UnappliedMigrationsError do
|
||||
defexception message: "Unapplied Migrations detected"
|
||||
end
|
||||
|
|
|
@ -1874,22 +1874,13 @@ defp truncate_field(%{"name" => name, "value" => value}) do
|
|||
end
|
||||
|
||||
def admin_api_update(user, params) do
|
||||
changeset =
|
||||
cast(user, params, [
|
||||
:is_moderator,
|
||||
:is_admin,
|
||||
:show_role
|
||||
])
|
||||
|
||||
with {:ok, updated_user} <- update_and_set_cache(changeset) do
|
||||
if user.is_admin != updated_user.is_admin do
|
||||
# Admin status change results in change of accessible OAuth scopes, and instead of changing
|
||||
# already issued tokens we revoke them, requiring user to sign in again
|
||||
global_sign_out(user)
|
||||
end
|
||||
|
||||
{:ok, updated_user}
|
||||
end
|
||||
user
|
||||
|> cast(params, [
|
||||
:is_moderator,
|
||||
:is_admin,
|
||||
:show_role
|
||||
])
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
@doc "Signs user out of all applications"
|
||||
|
|
|
@ -20,7 +20,7 @@ def filter(%{"type" => message_type} = message) do
|
|||
with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]),
|
||||
rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]),
|
||||
true <-
|
||||
length(accepted_vocabulary) == 0 || Enum.member?(accepted_vocabulary, message_type),
|
||||
Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, message_type),
|
||||
false <-
|
||||
length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type),
|
||||
{:ok, _} <- filter(message["object"]) do
|
||||
|
|
|
@ -658,24 +658,8 @@ def handle_incoming(
|
|||
with %User{ap_id: ^actor_id} = actor <- User.get_cached_by_ap_id(object["id"]) do
|
||||
{:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
|
||||
|
||||
locked = new_user_data[:locked] || false
|
||||
attachment = get_in(new_user_data, [:source_data, "attachment"]) || []
|
||||
invisible = new_user_data[:invisible] || false
|
||||
|
||||
fields =
|
||||
attachment
|
||||
|> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
|
||||
|> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
|
||||
|
||||
update_data =
|
||||
new_user_data
|
||||
|> Map.take([:avatar, :banner, :bio, :name, :also_known_as])
|
||||
|> Map.put(:fields, fields)
|
||||
|> Map.put(:locked, locked)
|
||||
|> Map.put(:invisible, invisible)
|
||||
|
||||
actor
|
||||
|> User.upgrade_changeset(update_data, true)
|
||||
|> User.upgrade_changeset(new_user_data, true)
|
||||
|> User.update_and_set_cache()
|
||||
|
||||
ActivityPub.update(%{
|
||||
|
|
|
@ -32,19 +32,14 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["read:accounts"], admin: true}
|
||||
when action in [:list_users, :user_show, :right_get, :invites]
|
||||
when action in [:list_users, :user_show, :right_get]
|
||||
)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write:accounts"], admin: true}
|
||||
when action in [
|
||||
:get_invite_token,
|
||||
:revoke_invite,
|
||||
:email_invite,
|
||||
:get_password_reset,
|
||||
:user_follow,
|
||||
:user_unfollow,
|
||||
:user_delete,
|
||||
:users_create,
|
||||
:user_toggle_activation,
|
||||
|
@ -57,6 +52,20 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
]
|
||||
)
|
||||
|
||||
plug(OAuthScopesPlug, %{scopes: ["read:invites"], admin: true} when action == :invites)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write:invites"], admin: true}
|
||||
when action in [:create_invite_token, :revoke_invite, :email_invite]
|
||||
)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write:follows"], admin: true}
|
||||
when action in [:user_follow, :user_unfollow, :relay_follow, :relay_unfollow]
|
||||
)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["read:reports"], admin: true}
|
||||
|
@ -66,7 +75,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write:reports"], admin: true}
|
||||
when action in [:report_update_state, :report_respond]
|
||||
when action in [:reports_update]
|
||||
)
|
||||
|
||||
plug(
|
||||
|
@ -90,7 +99,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write"], admin: true}
|
||||
when action in [:relay_follow, :relay_unfollow, :config_update]
|
||||
when action == :config_update
|
||||
)
|
||||
|
||||
@users_page_size 50
|
||||
|
@ -630,7 +639,7 @@ def get_password_reset(conn, %{"nickname" => nickname}) do
|
|||
def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
|
||||
users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
|
||||
|
||||
Enum.map(users, &User.force_password_reset_async/1)
|
||||
Enum.each(users, &User.force_password_reset_async/1)
|
||||
|
||||
ModerationLog.insert_log(%{
|
||||
actor: admin,
|
||||
|
|
|
@ -85,9 +85,13 @@ def delete(activity_id, user) do
|
|||
def repeat(id_or_ap_id, user, params \\ %{}) do
|
||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||
object <- Object.normalize(activity),
|
||||
nil <- Utils.get_existing_announce(user.ap_id, object),
|
||||
announce_activity <- Utils.get_existing_announce(user.ap_id, object),
|
||||
public <- public_announce?(object, params) do
|
||||
ActivityPub.announce(user, object, nil, true, public)
|
||||
if announce_activity do
|
||||
{:ok, announce_activity, object}
|
||||
else
|
||||
ActivityPub.announce(user, object, nil, true, public)
|
||||
end
|
||||
else
|
||||
_ -> {:error, dgettext("errors", "Could not repeat")}
|
||||
end
|
||||
|
@ -105,8 +109,12 @@ def unrepeat(id_or_ap_id, user) do
|
|||
def favorite(id_or_ap_id, user) do
|
||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||
object <- Object.normalize(activity),
|
||||
nil <- Utils.get_existing_like(user.ap_id, object) do
|
||||
ActivityPub.like(user, object)
|
||||
like_activity <- Utils.get_existing_like(user.ap_id, object) do
|
||||
if like_activity do
|
||||
{:ok, like_activity, object}
|
||||
else
|
||||
ActivityPub.like(user, object)
|
||||
end
|
||||
else
|
||||
_ -> {:error, dgettext("errors", "Could not favorite")}
|
||||
end
|
||||
|
|
|
@ -23,6 +23,23 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
|
|||
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
|
||||
|
||||
# GET /api/v1/notifications
|
||||
def index(conn, %{"account_id" => account_id} = params) do
|
||||
case Pleroma.User.get_cached_by_id(account_id) do
|
||||
%{ap_id: account_ap_id} ->
|
||||
params =
|
||||
params
|
||||
|> Map.delete("account_id")
|
||||
|> Map.put("account_ap_id", account_ap_id)
|
||||
|
||||
index(conn, params)
|
||||
|
||||
_ ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json(%{"error" => "Account is not found"})
|
||||
end
|
||||
end
|
||||
|
||||
def index(%{assigns: %{user: user}} = conn, params) do
|
||||
notifications = MastodonAPI.get_notifications(user, params)
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ defp do_search(version, %{assigns: %{user: user}} = conn, %{"q" => query} = para
|
|||
result =
|
||||
default_values
|
||||
|> Enum.map(fn {resource, default_value} ->
|
||||
if params["type"] == nil or params["type"] == resource do
|
||||
if params["type"] in [nil, resource] do
|
||||
{resource, fn -> resource_search(version, resource, query, options) end}
|
||||
else
|
||||
{resource, fn -> default_value end}
|
||||
|
|
|
@ -6,9 +6,9 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionController do
|
|||
@moduledoc "The module represents functions to manage user subscriptions."
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Web.MastodonAPI.PushSubscriptionView, as: View
|
||||
alias Pleroma.Web.Push
|
||||
alias Pleroma.Web.Push.Subscription
|
||||
alias Pleroma.Web.MastodonAPI.PushSubscriptionView, as: View
|
||||
|
||||
action_fallback(:errors)
|
||||
|
||||
|
|
|
@ -77,10 +77,7 @@ def public(%{assigns: %{user: user}} = conn, params) do
|
|||
|> render("index.json", activities: activities, for: user, as: :activity)
|
||||
end
|
||||
|
||||
# GET /api/v1/timelines/tag/:tag
|
||||
def hashtag(%{assigns: %{user: user}} = conn, params) do
|
||||
local_only = truthy_param?(params["local"])
|
||||
|
||||
def hashtag_fetching(params, user, local_only) do
|
||||
tags =
|
||||
[params["tag"], params["any"]]
|
||||
|> List.flatten()
|
||||
|
@ -98,7 +95,7 @@ def hashtag(%{assigns: %{user: user}} = conn, params) do
|
|||
|> Map.get("none", [])
|
||||
|> Enum.map(&String.downcase(&1))
|
||||
|
||||
activities =
|
||||
_activities =
|
||||
params
|
||||
|> Map.put("type", "Create")
|
||||
|> Map.put("local_only", local_only)
|
||||
|
@ -109,6 +106,13 @@ def hashtag(%{assigns: %{user: user}} = conn, params) do
|
|||
|> Map.put("tag_all", tag_all)
|
||||
|> Map.put("tag_reject", tag_reject)
|
||||
|> ActivityPub.fetch_public_activities()
|
||||
end
|
||||
|
||||
# GET /api/v1/timelines/tag/:tag
|
||||
def hashtag(%{assigns: %{user: user}} = conn, params) do
|
||||
local_only = truthy_param?(params["local"])
|
||||
|
||||
activities = hashtag_fetching(params, user, local_only)
|
||||
|
||||
conn
|
||||
|> add_link_headers(activities, %{"local" => local_only})
|
||||
|
|
|
@ -56,6 +56,7 @@ def get_notifications(user, params \\ %{}) do
|
|||
user
|
||||
|> Notification.for_user_query(options)
|
||||
|> restrict(:exclude_types, options)
|
||||
|> restrict(:account_ap_id, options)
|
||||
|> Pagination.fetch_paginated(params)
|
||||
end
|
||||
|
||||
|
@ -71,7 +72,8 @@ defp cast_params(params) do
|
|||
exclude_visibilities: {:array, :string},
|
||||
reblogs: :boolean,
|
||||
with_muted: :boolean,
|
||||
with_move: :boolean
|
||||
with_move: :boolean,
|
||||
account_ap_id: :string
|
||||
}
|
||||
|
||||
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
|
||||
|
@ -88,5 +90,9 @@ defp restrict(query, :exclude_types, %{exclude_types: mastodon_types = [_ | _]})
|
|||
|> where([q, a], not fragment("? @> ARRAY[?->>'type']::varchar[]", ^ap_types, a.data))
|
||||
end
|
||||
|
||||
defp restrict(query, :account_ap_id, %{account_ap_id: account_ap_id}) do
|
||||
where(query, [n, a], a.actor == ^account_ap_id)
|
||||
end
|
||||
|
||||
defp restrict(query, _, _), do: query
|
||||
end
|
||||
|
|
|
@ -253,6 +253,16 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
|||
nil
|
||||
end
|
||||
|
||||
emoji_reactions =
|
||||
with %{data: %{"reactions" => emoji_reactions}} <- object do
|
||||
Enum.map(emoji_reactions, fn {emoji, users} ->
|
||||
{emoji, length(users)}
|
||||
end)
|
||||
|> Enum.into(%{})
|
||||
else
|
||||
_ -> %{}
|
||||
end
|
||||
|
||||
%{
|
||||
id: to_string(activity.id),
|
||||
uri: object.data["id"],
|
||||
|
@ -293,7 +303,8 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
|||
spoiler_text: %{"text/plain" => summary_plaintext},
|
||||
expires_at: expires_at,
|
||||
direct_conversation_id: direct_conversation_id,
|
||||
thread_muted: thread_muted?
|
||||
thread_muted: thread_muted?,
|
||||
emoji_reactions: emoji_reactions
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
|
@ -14,10 +14,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
alias Pleroma.Web.ControllerHelper
|
||||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Web.OAuth.Authorization
|
||||
alias Pleroma.Web.OAuth.Scopes
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken
|
||||
alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
|
||||
alias Pleroma.Web.OAuth.Scopes
|
||||
|
||||
require Logger
|
||||
|
||||
|
@ -222,7 +222,7 @@ def token_exchange(
|
|||
{:user_active, true} <- {:user_active, !user.deactivated},
|
||||
{:password_reset_pending, false} <-
|
||||
{:password_reset_pending, user.password_reset_pending},
|
||||
{:ok, scopes} <- validate_scopes(app, params, user),
|
||||
{:ok, scopes} <- validate_scopes(app, params),
|
||||
{:ok, auth} <- Authorization.create_authorization(app, user, scopes),
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
json(conn, Token.Response.build(user, token))
|
||||
|
@ -471,7 +471,7 @@ defp do_create_authorization(
|
|||
{:get_user, (user && {:ok, user}) || Authenticator.get_user(conn)},
|
||||
%App{} = app <- Repo.get_by(App, client_id: client_id),
|
||||
true <- redirect_uri in String.split(app.redirect_uris),
|
||||
{:ok, scopes} <- validate_scopes(app, auth_attrs, user),
|
||||
{:ok, scopes} <- validate_scopes(app, auth_attrs),
|
||||
{:auth_active, true} <- {:auth_active, User.auth_active?(user)} do
|
||||
Authorization.create_authorization(app, user, scopes)
|
||||
end
|
||||
|
@ -487,12 +487,12 @@ defp get_session_registration_id(%Plug.Conn{} = conn), do: get_session(conn, :re
|
|||
defp put_session_registration_id(%Plug.Conn{} = conn, registration_id),
|
||||
do: put_session(conn, :registration_id, registration_id)
|
||||
|
||||
@spec validate_scopes(App.t(), map(), User.t()) ::
|
||||
@spec validate_scopes(App.t(), map()) ::
|
||||
{:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
|
||||
defp validate_scopes(%App{} = app, params, %User{} = user) do
|
||||
defp validate_scopes(%App{} = app, params) do
|
||||
params
|
||||
|> Scopes.fetch_scopes(app.scopes)
|
||||
|> Scopes.validate(app.scopes, user)
|
||||
|> Scopes.validate(app.scopes)
|
||||
end
|
||||
|
||||
def default_redirect_uri(%App{} = app) do
|
||||
|
|
|
@ -8,7 +8,6 @@ defmodule Pleroma.Web.OAuth.Scopes do
|
|||
"""
|
||||
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
alias Pleroma.User
|
||||
|
||||
@doc """
|
||||
Fetch scopes from request params.
|
||||
|
@ -56,35 +55,18 @@ def to_string(scopes), do: Enum.join(scopes, " ")
|
|||
@doc """
|
||||
Validates scopes.
|
||||
"""
|
||||
@spec validate(list() | nil, list(), User.t()) ::
|
||||
@spec validate(list() | nil, list()) ::
|
||||
{:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
|
||||
def validate(blank_scopes, _app_scopes, _user) when blank_scopes in [nil, []],
|
||||
def validate(blank_scopes, _app_scopes) when blank_scopes in [nil, []],
|
||||
do: {:error, :missing_scopes}
|
||||
|
||||
def validate(scopes, app_scopes, %User{} = user) do
|
||||
with {:ok, _} <- ensure_scopes_support(scopes, app_scopes),
|
||||
{:ok, scopes} <- authorize_admin_scopes(scopes, app_scopes, user) do
|
||||
{:ok, scopes}
|
||||
end
|
||||
end
|
||||
|
||||
defp ensure_scopes_support(scopes, app_scopes) do
|
||||
def validate(scopes, app_scopes) do
|
||||
case OAuthScopesPlug.filter_descendants(scopes, app_scopes) do
|
||||
^scopes -> {:ok, scopes}
|
||||
_ -> {:error, :unsupported_scopes}
|
||||
end
|
||||
end
|
||||
|
||||
defp authorize_admin_scopes(scopes, app_scopes, %User{} = user) do
|
||||
if user.is_admin || !contains_admin_scopes?(scopes) || !contains_admin_scopes?(app_scopes) do
|
||||
{:ok, scopes}
|
||||
else
|
||||
# Gracefully dropping admin scopes from requested scopes if user isn't an admin (not raising)
|
||||
scopes = scopes -- OAuthScopesPlug.filter_descendants(scopes, ["admin"])
|
||||
validate(scopes, app_scopes, user)
|
||||
end
|
||||
end
|
||||
|
||||
def contains_admin_scopes?(scopes) do
|
||||
scopes
|
||||
|> OAuthScopesPlug.filter_descendants(["admin"])
|
||||
|
|
|
@ -23,7 +23,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
|
|||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["read:statuses"]}
|
||||
when action in [:conversation, :conversation_statuses, :emoji_reactions_by]
|
||||
when action in [:conversation, :conversation_statuses]
|
||||
)
|
||||
|
||||
plug(
|
||||
|
|
2
mix.exs
2
mix.exs
|
@ -124,7 +124,7 @@ defp deps do
|
|||
{:earmark, "~> 1.3"},
|
||||
{:bbcode, "~> 0.1.1"},
|
||||
{:ex_machina, "~> 2.3", only: :test},
|
||||
{:credo, "~> 0.9.3", only: [:dev, :test]},
|
||||
{:credo, "~> 1.1.0", only: [:dev, :test], runtime: false},
|
||||
{:mock, "~> 0.3.3", only: :test},
|
||||
{:crypt,
|
||||
git: "https://github.com/msantos/crypt", ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"},
|
||||
|
|
4
mix.lock
4
mix.lock
|
@ -16,7 +16,7 @@
|
|||
"cors_plug": {:hex, :cors_plug, "1.5.2", "72df63c87e4f94112f458ce9d25800900cc88608c1078f0e4faddf20933eda6e", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm"},
|
||||
"credo": {:hex, :credo, "0.9.3", "76fa3e9e497ab282e0cf64b98a624aa11da702854c52c82db1bf24e54ab7c97a", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"credo": {:hex, :credo, "1.1.5", "caec7a3cadd2e58609d7ee25b3931b129e739e070539ad1a0cd7efeeb47014f4", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]},
|
||||
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm"},
|
||||
|
@ -63,7 +63,7 @@
|
|||
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
|
||||
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
|
||||
"mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"},
|
||||
"mock": {:hex, :mock, "0.3.3", "42a433794b1291a9cf1525c6d26b38e039e0d3a360732b5e467bfc77ef26c914", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"mock": {:hex, :mock, "0.3.4", "c5862eb3b8c64237f45f586cf00c9d892ba07bb48305a43319d428ce3c2897dd", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"},
|
||||
"mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm"},
|
||||
"myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]},
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
defmodule Pleroma.Repo.Migrations.AddScopesToPleromaFEOAuthRecords do
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
update_scopes_clause = "SET scopes = '{read,write,follow,push,admin}'"
|
||||
apps_where = "WHERE apps.client_name like 'PleromaFE_%' or apps.client_name like 'AdminFE_%'"
|
||||
app_id_subquery_where = "WHERE app_id IN (SELECT apps.id FROM apps #{apps_where})"
|
||||
|
||||
execute("UPDATE apps #{update_scopes_clause} #{apps_where}")
|
||||
|
||||
for table <- ["oauth_authorizations", "oauth_tokens"] do
|
||||
execute("UPDATE #{table} #{update_scopes_clause} #{app_id_subquery_where}")
|
||||
end
|
||||
end
|
||||
|
||||
def down, do: :noop
|
||||
end
|
|
@ -1 +1 @@
|
|||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/vendors~app.b2603a50868c68a1c192.css rel=stylesheet><link href=/static/css/app.ae04505b31bb0ee2765e.css rel=stylesheet><link href=/static/fontello.1576166651574.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.3f1ed7a4fdfc37ee27a7.js></script><script type=text/javascript src=/static/js/app.a9b3f4c3e79baf3fa8b7.js></script></body></html>
|
||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/vendors~app.b2603a50868c68a1c192.css rel=stylesheet><link href=/static/css/app.ae04505b31bb0ee2765e.css rel=stylesheet><link href=/static/fontello.1579102213354.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.86bc6d5e06d2e17976c5.js></script><script type=text/javascript src=/static/js/app.a43640742dacfb13b6b0.js></script></body></html>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
14
priv/static/static/font/fontello.1576166651574.svg → priv/static/static/font/fontello.1579102213354.svg
Executable file → Normal file
14
priv/static/static/font/fontello.1576166651574.svg → priv/static/static/font/fontello.1579102213354.svg
Executable file → Normal file
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>Copyright (C) 2019 by original authors @ fontello.com</metadata>
|
||||
<metadata>Copyright (C) 2020 by original authors @ fontello.com</metadata>
|
||||
<defs>
|
||||
<font id="fontello" horiz-adv-x="1000" >
|
||||
<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="857" descent="-143" />
|
||||
|
@ -64,6 +64,18 @@
|
|||
|
||||
<glyph glyph-name="zoom-in" unicode="" d="M571 411v-36q0-7-5-13t-12-5h-125v-125q0-7-6-13t-12-5h-36q-7 0-13 5t-5 13v125h-125q-7 0-12 5t-6 13v36q0 7 6 12t12 5h125v125q0 8 5 13t13 5h36q7 0 12-5t6-13v-125h125q7 0 12-5t5-12z m72-18q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-21-50t-51-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
|
||||
|
||||
<glyph glyph-name="users" unicode="" d="M331 357q-90-3-148-71h-75q-45 0-77 22t-31 66q0 197 69 197 4 0 25-11t54-24 66-12q38 0 75 13-3-21-3-37 0-78 45-143z m598-355q0-67-41-106t-108-39h-488q-68 0-108 39t-41 106q0 29 2 57t8 61 14 61 24 54 35 45 48 30 62 11q6 0 24-12t41-26 59-27 76-12 75 12 60 27 41 26 24 12q34 0 62-11t47-30 35-45 24-54 15-61 8-61 2-57z m-572 712q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z m393-214q0-89-63-152t-151-62-152 62-63 152 63 151 152 63 151-63 63-151z m321-126q0-43-31-66t-77-22h-75q-57 68-147 71 45 65 45 143 0 16-3 37 37-13 74-13 33 0 67 12t54 24 24 11q69 0 69-197z m-71 340q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z" horiz-adv-x="1071.4" />
|
||||
|
||||
<glyph glyph-name="chat" unicode="" d="M786 428q0-77-53-143t-143-104-197-38q-48 0-98 9-70-49-155-72-21-5-48-9h-2q-6 0-12 5t-6 12q-1 1-1 3t1 4 1 3l1 3t2 3 2 3 3 3 2 2q3 3 13 14t15 16 12 17 14 21 11 25q-69 40-108 98t-40 125q0 78 53 144t143 104 197 38 197-38 143-104 53-144z m214-142q0-67-40-126t-108-98q5-14 11-25t14-21 13-16 14-17 13-14q0 0 2-2t3-3 2-3 2-3l1-3t1-3 1-4-1-3q-2-8-7-13t-12-4q-28 4-48 9-86 23-156 72-50-9-98-9-151 0-263 74 32-3 49-3 90 0 172 25t148 72q69 52 107 119t37 141q0 43-13 85 72-39 114-99t42-128z" horiz-adv-x="1000" />
|
||||
|
||||
<glyph glyph-name="info-circled" unicode="" d="M571 89v89q0 8-5 13t-12 5h-54v286q0 8-5 13t-13 5h-178q-8 0-13-5t-5-13v-89q0-8 5-13t13-5h53v-179h-53q-8 0-13-5t-5-13v-89q0-8 5-13t13-5h250q7 0 12 5t5 13z m-71 500v89q0 8-5 13t-13 5h-107q-8 0-13-5t-5-13v-89q0-8 5-13t13-5h107q8 0 13 5t5 13z m357-232q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
|
||||
|
||||
<glyph glyph-name="login" unicode="" d="M661 357q0-14-11-25l-303-304q-11-10-26-10t-25 10-10 25v161h-250q-15 0-25 11t-11 25v214q0 15 11 25t25 11h250v161q0 14 10 25t25 10 26-10l303-304q11-10 11-25z m196 196v-392q0-67-47-114t-114-47h-178q-7 0-13 5t-5 13q0 2-1 11t0 15 2 13 5 11 12 3h178q37 0 64 27t26 63v392q0 37-26 64t-64 26h-174t-6 0-6 2-5 3-4 5-1 8q0 2-1 11t0 15 2 13 5 11 12 3h178q67 0 114-47t47-114z" horiz-adv-x="857.1" />
|
||||
|
||||
<glyph glyph-name="home-2" unicode="" d="M521 826q322-279 500-429 20-16 20-40 0-21-15-37t-36-15l-105 0 0-364q0-21-15-37t-36-16l-156 0q-22 0-37 16t-16 37l0 208-209 0 0-208q0-21-15-37t-36-16l-156 0q-21 0-37 16t-16 37l0 364-103 0q-22 0-37 15t-16 37 19 40z" horiz-adv-x="1041" />
|
||||
|
||||
<glyph glyph-name="arrow-curved" unicode="" d="M799 302l0-56 112 0-223-223-224 223 112 0 0 56q0 116-81 197t-197 82-198-82-82-197q0 162 115 276t276 114 276-114 114-276z" horiz-adv-x="928" />
|
||||
|
||||
<glyph glyph-name="spin3" unicode="" d="M494 857c-266 0-483-210-494-472-1-19 13-20 13-20l84 0c16 0 19 10 19 18 10 199 176 358 378 358 107 0 205-45 273-118l-58-57c-11-12-11-27 5-31l247-50c21-5 46 11 37 44l-58 227c-2 9-16 22-29 13l-65-60c-89 91-214 148-352 148z m409-508c-16 0-19-10-19-18-10-199-176-358-377-358-108 0-205 45-274 118l59 57c10 12 10 27-5 31l-248 50c-21 5-46-11-37-44l58-227c2-9 16-22 30-13l64 60c89-91 214-148 353-148 265 0 482 210 493 473 1 18-13 19-13 19l-84 0z" horiz-adv-x="1000" />
|
||||
|
||||
<glyph glyph-name="spin4" unicode="" d="M498 857c-114 0-228-39-320-116l0 0c173 140 428 130 588-31 134-134 164-332 89-495-10-29-5-50 12-68 21-20 61-23 84 0 3 3 12 15 15 24 71 180 33 393-112 539-99 98-228 147-356 147z m-409-274c-14 0-29-5-39-16-3-3-13-15-15-24-71-180-34-393 112-539 185-185 479-195 676-31l0 0c-173-140-428-130-589 31-134 134-163 333-89 495 11 29 6 50-12 68-11 11-27 17-44 16z" horiz-adv-x="1001" />
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 24 KiB |
Binary file not shown.
BIN
priv/static/static/font/fontello.1579102213354.woff
Normal file
BIN
priv/static/static/font/fontello.1579102213354.woff
Normal file
Binary file not shown.
BIN
priv/static/static/font/fontello.1579102213354.woff2
Normal file
BIN
priv/static/static/font/fontello.1579102213354.woff2
Normal file
Binary file not shown.
|
@ -1,11 +1,11 @@
|
|||
@font-face {
|
||||
font-family: "Icons";
|
||||
src: url("./font/fontello.1576166651574.eot");
|
||||
src: url("./font/fontello.1576166651574.eot") format("embedded-opentype"),
|
||||
url("./font/fontello.1576166651574.woff2") format("woff2"),
|
||||
url("./font/fontello.1576166651574.woff") format("woff"),
|
||||
url("./font/fontello.1576166651574.ttf") format("truetype"),
|
||||
url("./font/fontello.1576166651574.svg") format("svg");
|
||||
src: url("./font/fontello.1579102213354.eot");
|
||||
src: url("./font/fontello.1579102213354.eot") format("embedded-opentype"),
|
||||
url("./font/fontello.1579102213354.woff2") format("woff2"),
|
||||
url("./font/fontello.1579102213354.woff") format("woff"),
|
||||
url("./font/fontello.1579102213354.ttf") format("truetype"),
|
||||
url("./font/fontello.1579102213354.svg") format("svg");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
@ -122,3 +122,15 @@ .icon-bell-ringing-o::before { content: "\e810"; }
|
|||
.icon-zoom-in::before { content: "\e81c"; }
|
||||
|
||||
.icon-gauge::before { content: "\f0e4"; }
|
||||
|
||||
.icon-users::before { content: "\e81d"; }
|
||||
|
||||
.icon-info-circled::before { content: "\e81f"; }
|
||||
|
||||
.icon-home-2::before { content: "\e821"; }
|
||||
|
||||
.icon-chat::before { content: "\e81e"; }
|
||||
|
||||
.icon-login::before { content: "\e820"; }
|
||||
|
||||
.icon-arrow-curved::before { content: "\e822"; }
|
|
@ -303,6 +303,42 @@
|
|||
"css": "gauge",
|
||||
"code": 61668,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "31972e4e9d080eaa796290349ae6c1fd",
|
||||
"css": "users",
|
||||
"code": 59421,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "e82cedfa1d5f15b00c5a81c9bd731ea2",
|
||||
"css": "info-circled",
|
||||
"code": 59423,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "w3nzesrlbezu6f30q7ytyq919p6gdlb6",
|
||||
"css": "home-2",
|
||||
"code": 59425,
|
||||
"src": "typicons"
|
||||
},
|
||||
{
|
||||
"uid": "dcedf50ab1ede3283d7a6c70e2fe32f3",
|
||||
"css": "chat",
|
||||
"code": 59422,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "3a00327e61b997b58518bd43ed83c3df",
|
||||
"css": "login",
|
||||
"code": 59424,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "f3ebd6751c15a280af5cc5f4a764187d",
|
||||
"css": "arrow-curved",
|
||||
"code": 59426,
|
||||
"src": "iconic"
|
||||
}
|
||||
]
|
||||
}
|
2
priv/static/static/js/2.8896ea39a0ea8016391a.js
Normal file
2
priv/static/static/js/2.8896ea39a0ea8016391a.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
(window.webpackJsonp=window.webpackJsonp||[]).push([[2],{567:function(t,e,i){var c=i(568);"string"==typeof c&&(c=[[t.i,c,""]]),c.locals&&(t.exports=c.locals);(0,i(3).default)("cc6cdea4",c,!0,{})},568:function(t,e,i){(t.exports=i(2)(!1)).push([t.i,".sticker-picker{width:100%}.sticker-picker .contents{min-height:250px}.sticker-picker .contents .sticker-picker-content{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0 4px}.sticker-picker .contents .sticker-picker-content .sticker{display:-ms-flexbox;display:flex;-ms-flex:1 1 auto;flex:1 1 auto;margin:4px;width:56px;height:56px}.sticker-picker .contents .sticker-picker-content .sticker img{height:100%}.sticker-picker .contents .sticker-picker-content .sticker img:hover{filter:drop-shadow(0 0 5px var(--link,#d8a070))}",""])},569:function(t,e,i){"use strict";i.r(e);var c=i(88),n={components:{TabSwitcher:i(49).a},data:function(){return{meta:{stickers:[]},path:""}},computed:{pack:function(){return this.$store.state.instance.stickers||[]}},methods:{clear:function(){this.meta={stickers:[]}},pick:function(t,e){var i=this,n=this.$store;fetch(t).then(function(t){t.blob().then(function(t){var a=new File([t],e,{mimetype:"image/png"}),s=new FormData;s.append("file",a),c.a.uploadMedia({store:n,formData:s}).then(function(t){i.$emit("uploaded",t),i.clear()},function(t){console.warn("Can't attach sticker"),console.warn(t),i.$emit("upload-failed","default")})})})}}},a=i(0);var s=function(t){i(567)},r=Object(a.a)(n,function(){var t=this,e=t.$createElement,i=t._self._c||e;return i("div",{staticClass:"sticker-picker"},[i("tab-switcher",{staticClass:"tab-switcher",attrs:{"render-only-focused":!0,"scrollable-tabs":""}},t._l(t.pack,function(e){return i("div",{key:e.path,staticClass:"sticker-picker-content",attrs:{"image-tooltip":e.meta.title,image:e.path+e.meta.tabIcon}},t._l(e.meta.stickers,function(c){return i("div",{key:c,staticClass:"sticker",on:{click:function(i){i.stopPropagation(),i.preventDefault(),t.pick(e.path+c,e.meta.title)}}},[i("img",{attrs:{src:e.path+c}})])}),0)}),0)],1)},[],!1,s,null,null);e.default=r.exports}}]);
|
||||
//# sourceMappingURL=2.8896ea39a0ea8016391a.js.map
|
1
priv/static/static/js/2.8896ea39a0ea8016391a.js.map
Normal file
1
priv/static/static/js/2.8896ea39a0ea8016391a.js.map
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,2 +0,0 @@
|
|||
(window.webpackJsonp=window.webpackJsonp||[]).push([[2],{1023:function(t,e,i){"use strict";i.r(e);var n=i(1024),c=i.n(n);for(var r in n)"default"!==r&&function(t){i.d(e,t,function(){return n[t]})}(r);var a=i(1027),s=i(0);var o=function(t){i(1025)},u=Object(s.a)(c.a,a.a,a.b,!1,o,null,null);e.default=u.exports},1024:function(t,e,i){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var n=c(i(352));function c(t){return t&&t.__esModule?t:{default:t}}var r={components:{TabSwitcher:c(i(207)).default},data:function(){return{meta:{stickers:[]},path:""}},computed:{pack:function(){return this.$store.state.instance.stickers||[]}},methods:{clear:function(){this.meta={stickers:[]}},pick:function(t,e){var i=this,c=this.$store;fetch(t).then(function(t){t.blob().then(function(t){var r=new File([t],e,{mimetype:"image/png"}),a=new FormData;a.append("file",r),n.default.uploadMedia({store:c,formData:a}).then(function(t){i.$emit("uploaded",t),i.clear()},function(t){console.warn("Can't attach sticker"),console.warn(t),i.$emit("upload-failed","default")})})})}}};e.default=r},1025:function(t,e,i){var n=i(1026);"string"==typeof n&&(n=[[t.i,n,""]]),n.locals&&(t.exports=n.locals);(0,i(2).default)("cc6cdea4",n,!0,{})},1026:function(t,e,i){(t.exports=i(1)(!1)).push([t.i,".sticker-picker{width:100%;position:relative}.sticker-picker .tab-switcher{position:absolute;top:0;bottom:0;left:0;right:0}.sticker-picker .sticker-picker-content .sticker{display:inline-block;width:20%;height:20%}.sticker-picker .sticker-picker-content .sticker img{width:100%}.sticker-picker .sticker-picker-content .sticker img:hover{filter:drop-shadow(0 0 5px var(--link,#d8a070))}",""])},1027:function(t,e,i){"use strict";i.d(e,"a",function(){return n}),i.d(e,"b",function(){return c});var n=function(){var t=this,e=t.$createElement,i=t._self._c||e;return i("div",{staticClass:"sticker-picker"},[i("tab-switcher",{staticClass:"tab-switcher",attrs:{"render-only-focused":!0,"scrollable-tabs":""}},t._l(t.pack,function(e){return i("div",{key:e.path,staticClass:"sticker-picker-content",attrs:{"image-tooltip":e.meta.title,image:e.path+e.meta.tabIcon}},t._l(e.meta.stickers,function(n){return i("div",{key:n,staticClass:"sticker",on:{click:function(i){i.stopPropagation(),i.preventDefault(),t.pick(e.path+n,e.meta.title)}}},[i("img",{attrs:{src:e.path+n}})])}),0)}),0)],1)},c=[]}}]);
|
||||
//# sourceMappingURL=2.c96b30ae9f2d3f46f0ad.js.map
|
2
priv/static/static/js/app.a43640742dacfb13b6b0.js
Normal file
2
priv/static/static/js/app.a43640742dacfb13b6b0.js
Normal file
File diff suppressed because one or more lines are too long
1
priv/static/static/js/app.a43640742dacfb13b6b0.js.map
Normal file
1
priv/static/static/js/app.a43640742dacfb13b6b0.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
85
priv/static/static/js/vendors~app.86bc6d5e06d2e17976c5.js
Normal file
85
priv/static/static/js/vendors~app.86bc6d5e06d2e17976c5.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"pleroma-dark": [ "Pleroma Dark", "#121a24", "#182230", "#b9b9ba", "#d8a070", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
|
||||
"pleroma-light": [ "Pleroma Light", "#f2f4f6", "#dbe0e8", "#304055", "#f86f0f", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
|
||||
"pleroma-amoled": [ "Pleroma Dark AMOLED", "#000000", "#111111", "#b0b0b1", "#d8a070", "#aa0000", "#0fa00f", "#0095ff", "#d59500"],
|
||||
"classic-dark": [ "Classic Dark", "#161c20", "#282e32", "#b9b9b9", "#baaa9c", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
|
||||
"bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"],
|
||||
"ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ],
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -745,7 +745,7 @@ test "it doesn't return notifications from a blocked user when with_muted is set
|
|||
|
||||
{:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"})
|
||||
|
||||
assert length(Notification.for_user(user, %{with_muted: true})) == 0
|
||||
assert Enum.empty?(Notification.for_user(user, %{with_muted: true}))
|
||||
end
|
||||
|
||||
test "it doesn't return notifications from a domain-blocked user when with_muted is set" do
|
||||
|
@ -755,7 +755,7 @@ test "it doesn't return notifications from a domain-blocked user when with_muted
|
|||
|
||||
{:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"})
|
||||
|
||||
assert length(Notification.for_user(user, %{with_muted: true})) == 0
|
||||
assert Enum.empty?(Notification.for_user(user, %{with_muted: true}))
|
||||
end
|
||||
|
||||
test "it returns notifications from muted threads when with_muted is set" do
|
||||
|
|
|
@ -4,7 +4,10 @@
|
|||
|
||||
defmodule Pleroma.RepoTest do
|
||||
use Pleroma.DataCase
|
||||
import ExUnit.CaptureLog
|
||||
import Pleroma.Factory
|
||||
import Mock
|
||||
|
||||
alias Pleroma.User
|
||||
|
||||
describe "find_resource/1" do
|
||||
|
@ -46,4 +49,44 @@ test "return error if has not assoc " do
|
|||
assert Repo.get_assoc(token, :user) == {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
describe "check_migrations_applied!" do
|
||||
setup_with_mocks([
|
||||
{Ecto.Migrator, [],
|
||||
[
|
||||
with_repo: fn repo, fun -> passthrough([repo, fun]) end,
|
||||
migrations: fn Pleroma.Repo ->
|
||||
[
|
||||
{:up, 20_191_128_153_944, "fix_missing_following_count"},
|
||||
{:up, 20_191_203_043_610, "create_report_notes"},
|
||||
{:down, 20_191_220_174_645, "add_scopes_to_pleroma_feo_auth_records"}
|
||||
]
|
||||
end
|
||||
]}
|
||||
]) do
|
||||
:ok
|
||||
end
|
||||
|
||||
test "raises if it detects unapplied migrations" do
|
||||
assert_raise Pleroma.Repo.UnappliedMigrationsError, fn ->
|
||||
capture_log(&Repo.check_migrations_applied!/0)
|
||||
end
|
||||
end
|
||||
|
||||
test "doesn't do anything if disabled" do
|
||||
disable_migration_check =
|
||||
Pleroma.Config.get([:i_am_aware_this_may_cause_data_loss, :disable_migration_check])
|
||||
|
||||
Pleroma.Config.put([:i_am_aware_this_may_cause_data_loss, :disable_migration_check], true)
|
||||
|
||||
on_exit(fn ->
|
||||
Pleroma.Config.put(
|
||||
[:i_am_aware_this_may_cause_data_loss, :disable_migration_check],
|
||||
disable_migration_check
|
||||
)
|
||||
end)
|
||||
|
||||
assert :ok == Repo.check_migrations_applied!()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -78,7 +78,7 @@ test "with locked accounts, it does not create a follow or an accept" do
|
|||
)
|
||||
|> Repo.all()
|
||||
|
||||
assert length(accepts) == 0
|
||||
assert Enum.empty?(accepts)
|
||||
end
|
||||
|
||||
test "it works for follow requests when you are already followed, creating a new accept activity" do
|
||||
|
|
|
@ -1363,6 +1363,30 @@ test "returns 404 when report id is invalid", %{conn: conn} do
|
|||
}
|
||||
end
|
||||
|
||||
test "requires admin:write:reports scope", %{conn: conn, id: id, admin: admin} do
|
||||
read_token = insert(:oauth_token, user: admin, scopes: ["admin:read"])
|
||||
write_token = insert(:oauth_token, user: admin, scopes: ["admin:write:reports"])
|
||||
|
||||
response =
|
||||
conn
|
||||
|> assign(:token, read_token)
|
||||
|> patch("/api/pleroma/admin/reports", %{
|
||||
"reports" => [%{"state" => "resolved", "id" => id}]
|
||||
})
|
||||
|> json_response(403)
|
||||
|
||||
assert response == %{
|
||||
"error" => "Insufficient permissions: admin:write:reports."
|
||||
}
|
||||
|
||||
conn
|
||||
|> assign(:token, write_token)
|
||||
|> patch("/api/pleroma/admin/reports", %{
|
||||
"reports" => [%{"state" => "resolved", "id" => id}]
|
||||
})
|
||||
|> json_response(:no_content)
|
||||
end
|
||||
|
||||
test "mark report as resolved", %{conn: conn, id: id, admin: admin} do
|
||||
conn
|
||||
|> patch("/api/pleroma/admin/reports", %{
|
||||
|
@ -2840,7 +2864,7 @@ test "GET /instances/:instance/statuses", %{conn: conn} do
|
|||
|
||||
response = json_response(ret_conn, 200)
|
||||
|
||||
assert length(response) == 0
|
||||
assert Enum.empty?(response)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -284,22 +284,22 @@ test "favoriting a status" do
|
|||
{:ok, %Activity{}, _} = CommonAPI.favorite(activity.id, user)
|
||||
end
|
||||
|
||||
test "retweeting a status twice returns an error" do
|
||||
test "retweeting a status twice returns the status" do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
|
||||
{:ok, %Activity{}, _object} = CommonAPI.repeat(activity.id, user)
|
||||
{:error, _} = CommonAPI.repeat(activity.id, user)
|
||||
{:ok, %Activity{} = activity, object} = CommonAPI.repeat(activity.id, user)
|
||||
{:ok, ^activity, ^object} = CommonAPI.repeat(activity.id, user)
|
||||
end
|
||||
|
||||
test "favoriting a status twice returns an error" do
|
||||
test "favoriting a status twice returns the status" do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
|
||||
{:ok, %Activity{}, _object} = CommonAPI.favorite(activity.id, user)
|
||||
{:error, _} = CommonAPI.favorite(activity.id, user)
|
||||
{:ok, %Activity{} = activity, object} = CommonAPI.favorite(activity.id, user)
|
||||
{:ok, ^activity, ^object} = CommonAPI.favorite(activity.id, user)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -307,7 +307,7 @@ test "for private posts, not a reply" do
|
|||
|
||||
{to, cc} = Utils.get_to_and_cc(user, mentions, nil, "private", nil)
|
||||
assert length(to) == 2
|
||||
assert length(cc) == 0
|
||||
assert Enum.empty?(cc)
|
||||
|
||||
assert mentioned_user.ap_id in to
|
||||
assert user.follower_address in to
|
||||
|
@ -323,7 +323,7 @@ test "for private posts, a reply" do
|
|||
{to, cc} = Utils.get_to_and_cc(user, mentions, activity, "private", nil)
|
||||
|
||||
assert length(to) == 3
|
||||
assert length(cc) == 0
|
||||
assert Enum.empty?(cc)
|
||||
|
||||
assert mentioned_user.ap_id in to
|
||||
assert third_user.ap_id in to
|
||||
|
@ -338,7 +338,7 @@ test "for direct posts, not a reply" do
|
|||
{to, cc} = Utils.get_to_and_cc(user, mentions, nil, "direct", nil)
|
||||
|
||||
assert length(to) == 1
|
||||
assert length(cc) == 0
|
||||
assert Enum.empty?(cc)
|
||||
|
||||
assert mentioned_user.ap_id in to
|
||||
end
|
||||
|
@ -353,7 +353,7 @@ test "for direct posts, a reply" do
|
|||
{to, cc} = Utils.get_to_and_cc(user, mentions, activity, "direct", nil)
|
||||
|
||||
assert length(to) == 2
|
||||
assert length(cc) == 0
|
||||
assert Enum.empty?(cc)
|
||||
|
||||
assert mentioned_user.ap_id in to
|
||||
assert third_user.ap_id in to
|
||||
|
|
|
@ -457,6 +457,30 @@ test "preserves parameters in link headers" do
|
|||
end
|
||||
end
|
||||
|
||||
describe "from specified user" do
|
||||
test "account_id" do
|
||||
%{user: user, conn: conn} = oauth_access(["read:notifications"])
|
||||
|
||||
%{id: account_id} = other_user1 = insert(:user)
|
||||
other_user2 = insert(:user)
|
||||
|
||||
{:ok, _activity} = CommonAPI.post(other_user1, %{"status" => "hi @#{user.nickname}"})
|
||||
{:ok, _activity} = CommonAPI.post(other_user2, %{"status" => "bye @#{user.nickname}"})
|
||||
|
||||
assert [%{"account" => %{"id" => ^account_id}}] =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/notifications", %{account_id: account_id})
|
||||
|> json_response(200)
|
||||
|
||||
assert %{"error" => "Account is not found"} =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/notifications", %{account_id: "cofe"})
|
||||
|> json_response(404)
|
||||
end
|
||||
end
|
||||
|
||||
defp get_notification_id_by_activity(%{id: id}) do
|
||||
Notification
|
||||
|> Repo.get_by(activity_id: id)
|
||||
|
|
|
@ -53,7 +53,8 @@ test "search", %{conn: conn} do
|
|||
{:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"})
|
||||
|
||||
results =
|
||||
get(conn, "/api/v2/search", %{"q" => "2hu #private"})
|
||||
conn
|
||||
|> get("/api/v2/search", %{"q" => "2hu #private"})
|
||||
|> json_response(200)
|
||||
|
||||
[account | _] = results["accounts"]
|
||||
|
@ -73,6 +74,30 @@ test "search", %{conn: conn} do
|
|||
[status] = results["statuses"]
|
||||
assert status["id"] == to_string(activity.id)
|
||||
end
|
||||
|
||||
test "excludes a blocked users from search results", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
user_smith = insert(:user, %{nickname: "Agent", name: "I love 2hu"})
|
||||
user_neo = insert(:user, %{nickname: "Agent Neo", name: "Agent"})
|
||||
|
||||
{:ok, act1} = CommonAPI.post(user, %{"status" => "This is about 2hu private 天子"})
|
||||
{:ok, act2} = CommonAPI.post(user_smith, %{"status" => "Agent Smith"})
|
||||
{:ok, act3} = CommonAPI.post(user_neo, %{"status" => "Agent Smith"})
|
||||
Pleroma.User.block(user, user_smith)
|
||||
|
||||
results =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> assign(:token, insert(:oauth_token, user: user, scopes: ["read"]))
|
||||
|> get("/api/v2/search", %{"q" => "Agent"})
|
||||
|> json_response(200)
|
||||
|
||||
status_ids = Enum.map(results["statuses"], fn g -> g["id"] end)
|
||||
|
||||
assert act3.id in status_ids
|
||||
refute act2.id in status_ids
|
||||
refute act1.id in status_ids
|
||||
end
|
||||
end
|
||||
|
||||
describe ".account_search" do
|
||||
|
@ -146,11 +171,10 @@ test "search", %{conn: conn} do
|
|||
|
||||
{:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"})
|
||||
|
||||
conn =
|
||||
results =
|
||||
conn
|
||||
|> get("/api/v1/search", %{"q" => "2hu"})
|
||||
|
||||
assert results = json_response(conn, 200)
|
||||
|> json_response(200)
|
||||
|
||||
[account | _] = results["accounts"]
|
||||
assert account["id"] == to_string(user_three.id)
|
||||
|
@ -168,11 +192,10 @@ test "search fetches remote statuses and prefers them over other results", %{con
|
|||
"status" => "check out https://shitposter.club/notice/2827873"
|
||||
})
|
||||
|
||||
conn =
|
||||
results =
|
||||
conn
|
||||
|> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"})
|
||||
|
||||
assert results = json_response(conn, 200)
|
||||
|> json_response(200)
|
||||
|
||||
[status, %{"id" => ^activity_id}] = results["statuses"]
|
||||
|
||||
|
@ -189,11 +212,10 @@ test "search doesn't show statuses that it shouldn't", %{conn: conn} do
|
|||
})
|
||||
|
||||
capture_log(fn ->
|
||||
conn =
|
||||
results =
|
||||
conn
|
||||
|> get("/api/v1/search", %{"q" => Object.normalize(activity).data["id"]})
|
||||
|
||||
assert results = json_response(conn, 200)
|
||||
|> json_response(200)
|
||||
|
||||
[] = results["statuses"]
|
||||
end)
|
||||
|
@ -202,23 +224,23 @@ test "search doesn't show statuses that it shouldn't", %{conn: conn} do
|
|||
test "search fetches remote accounts", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
conn =
|
||||
results =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> assign(:token, insert(:oauth_token, user: user, scopes: ["read"]))
|
||||
|> get("/api/v1/search", %{"q" => "mike@osada.macgirvin.com", "resolve" => "true"})
|
||||
|> json_response(200)
|
||||
|
||||
assert results = json_response(conn, 200)
|
||||
[account] = results["accounts"]
|
||||
assert account["acct"] == "mike@osada.macgirvin.com"
|
||||
end
|
||||
|
||||
test "search doesn't fetch remote accounts if resolve is false", %{conn: conn} do
|
||||
conn =
|
||||
results =
|
||||
conn
|
||||
|> get("/api/v1/search", %{"q" => "mike@osada.macgirvin.com", "resolve" => "false"})
|
||||
|> json_response(200)
|
||||
|
||||
assert results = json_response(conn, 200)
|
||||
assert [] == results["accounts"]
|
||||
end
|
||||
|
||||
|
|
|
@ -638,6 +638,13 @@ test "favs a status and returns it", %{conn: conn} do
|
|||
assert to_string(activity.id) == id
|
||||
end
|
||||
|
||||
test "favoriting twice will just return 200", %{conn: conn} do
|
||||
activity = insert(:note_activity)
|
||||
|
||||
post(conn, "/api/v1/statuses/#{activity.id}/favourite")
|
||||
assert post(conn, "/api/v1/statuses/#{activity.id}/favourite") |> json_response(200)
|
||||
end
|
||||
|
||||
test "returns 400 error for a wrong id", %{conn: conn} do
|
||||
conn = post(conn, "/api/v1/statuses/1/favourite")
|
||||
|
||||
|
|
|
@ -24,6 +24,22 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
|
|||
:ok
|
||||
end
|
||||
|
||||
test "has an emoji reaction list" do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
third_user = insert(:user)
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "dae cofe??"})
|
||||
|
||||
{:ok, _, _} = CommonAPI.react_with_emoji(activity.id, user, "☕")
|
||||
{:ok, _, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
|
||||
{:ok, _, _} = CommonAPI.react_with_emoji(activity.id, third_user, "🍵")
|
||||
activity = Repo.get(Activity, activity.id)
|
||||
status = StatusView.render("show.json", activity: activity)
|
||||
|
||||
assert status[:pleroma][:emoji_reactions]["🍵"] == 1
|
||||
assert status[:pleroma][:emoji_reactions]["☕"] == 2
|
||||
end
|
||||
|
||||
test "loads and returns the direct conversation id when given the `with_direct_conversation_id` option" do
|
||||
user = insert(:user)
|
||||
|
||||
|
@ -172,7 +188,8 @@ test "a note activity" do
|
|||
spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])},
|
||||
expires_at: nil,
|
||||
direct_conversation_id: nil,
|
||||
thread_muted: false
|
||||
thread_muted: false,
|
||||
emoji_reactions: %{}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -568,29 +568,34 @@ test "with existing authentication and OOB `redirect_uri`, redirects to app with
|
|||
|
||||
describe "POST /oauth/authorize" do
|
||||
test "redirects with oauth authorization, " <>
|
||||
"keeping only non-admin scopes for non-admin user" do
|
||||
app = insert(:oauth_app, scopes: ["read", "write", "admin"])
|
||||
"granting requested app-supported scopes to both admin- and non-admin users" do
|
||||
app_scopes = ["read", "write", "admin", "secret_scope"]
|
||||
app = insert(:oauth_app, scopes: app_scopes)
|
||||
redirect_uri = OAuthController.default_redirect_uri(app)
|
||||
|
||||
non_admin = insert(:user, is_admin: false)
|
||||
admin = insert(:user, is_admin: true)
|
||||
scopes_subset = ["read:subscope", "write", "admin"]
|
||||
|
||||
for {user, expected_scopes} <- %{
|
||||
non_admin => ["read:subscope", "write"],
|
||||
admin => ["read:subscope", "write", "admin"]
|
||||
} do
|
||||
# In case scope param is missing, expecting _all_ app-supported scopes to be granted
|
||||
for user <- [non_admin, admin],
|
||||
{requested_scopes, expected_scopes} <-
|
||||
%{scopes_subset => scopes_subset, nil => app_scopes} do
|
||||
conn =
|
||||
build_conn()
|
||||
|> post("/oauth/authorize", %{
|
||||
"authorization" => %{
|
||||
"name" => user.nickname,
|
||||
"password" => "test",
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => redirect_uri,
|
||||
"scope" => "read:subscope write admin",
|
||||
"state" => "statepassed"
|
||||
post(
|
||||
build_conn(),
|
||||
"/oauth/authorize",
|
||||
%{
|
||||
"authorization" => %{
|
||||
"name" => user.nickname,
|
||||
"password" => "test",
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => redirect_uri,
|
||||
"scope" => requested_scopes,
|
||||
"state" => "statepassed"
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
target = redirected_to(conn)
|
||||
assert target =~ redirect_uri
|
||||
|
@ -631,34 +636,31 @@ test "returns 401 for wrong credentials", %{conn: conn} do
|
|||
assert result =~ "Invalid Username/Password"
|
||||
end
|
||||
|
||||
test "returns 401 for missing scopes " <>
|
||||
"(including all admin-only scopes for non-admin user)" do
|
||||
test "returns 401 for missing scopes" do
|
||||
user = insert(:user, is_admin: false)
|
||||
app = insert(:oauth_app, scopes: ["read", "write", "admin"])
|
||||
redirect_uri = OAuthController.default_redirect_uri(app)
|
||||
|
||||
for scope_param <- ["", "admin:read admin:write"] do
|
||||
result =
|
||||
build_conn()
|
||||
|> post("/oauth/authorize", %{
|
||||
"authorization" => %{
|
||||
"name" => user.nickname,
|
||||
"password" => "test",
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => redirect_uri,
|
||||
"state" => "statepassed",
|
||||
"scope" => scope_param
|
||||
}
|
||||
})
|
||||
|> html_response(:unauthorized)
|
||||
result =
|
||||
build_conn()
|
||||
|> post("/oauth/authorize", %{
|
||||
"authorization" => %{
|
||||
"name" => user.nickname,
|
||||
"password" => "test",
|
||||
"client_id" => app.client_id,
|
||||
"redirect_uri" => redirect_uri,
|
||||
"state" => "statepassed",
|
||||
"scope" => ""
|
||||
}
|
||||
})
|
||||
|> html_response(:unauthorized)
|
||||
|
||||
# Keep the details
|
||||
assert result =~ app.client_id
|
||||
assert result =~ redirect_uri
|
||||
# Keep the details
|
||||
assert result =~ app.client_id
|
||||
assert result =~ redirect_uri
|
||||
|
||||
# Error message
|
||||
assert result =~ "This action is outside the authorized scopes"
|
||||
end
|
||||
# Error message
|
||||
assert result =~ "This action is outside the authorized scopes"
|
||||
end
|
||||
|
||||
test "returns 401 for scopes beyond app scopes hierarchy", %{conn: conn} do
|
||||
|
|
|
@ -14,6 +14,10 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
|
|||
"emoji"
|
||||
)
|
||||
|
||||
clear_config([:auth, :enforce_oauth_admin_scope_usage]) do
|
||||
Pleroma.Config.put([:auth, :enforce_oauth_admin_scope_usage], false)
|
||||
end
|
||||
|
||||
test "shared & non-shared pack information in list_packs is ok" do
|
||||
conn = build_conn()
|
||||
resp = conn |> get(emoji_api_path(conn, :list_packs)) |> json_response(200)
|
||||
|
|
|
@ -57,11 +57,6 @@ test "GET /api/v1/pleroma/statuses/:id/emoji_reactions_by", %{conn: conn} do
|
|||
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"})
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> assign(:token, insert(:oauth_token, user: user, scopes: ["read:statuses"]))
|
||||
|
||||
result =
|
||||
conn
|
||||
|> get("/api/v1/pleroma/statuses/#{activity.id}/emoji_reactions_by")
|
||||
|
|
|
@ -55,7 +55,7 @@ test "it returns HTTP 200", %{conn: conn} do
|
|||
|
||||
user = refresh_record(user)
|
||||
assert Comeonin.Pbkdf2.checkpw("test", user.password_hash)
|
||||
assert length(Token.get_user_tokens(user)) == 0
|
||||
assert Enum.empty?(Token.get_user_tokens(user))
|
||||
end
|
||||
|
||||
test "it sets password_reset_pending to false", %{conn: conn} do
|
||||
|
|
Loading…
Reference in a new issue