forked from AkkomaGang/akkoma
[#923] Merge remote-tracking branch 'remotes/upstream/develop' into twitter_oauth
# Conflicts: # docs/config.md # test/support/factory.ex
This commit is contained in:
commit
bffddf5e31
32 changed files with 1119 additions and 39 deletions
|
@ -8,6 +8,10 @@
|
||||||
# General application configuration
|
# General application configuration
|
||||||
config :pleroma, ecto_repos: [Pleroma.Repo]
|
config :pleroma, ecto_repos: [Pleroma.Repo]
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.Repo,
|
||||||
|
types: Pleroma.PostgresTypes,
|
||||||
|
telemetry_event: [Pleroma.Repo.Instrumenter]
|
||||||
|
|
||||||
config :pleroma, Pleroma.Captcha,
|
config :pleroma, Pleroma.Captcha,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
seconds_valid: 60,
|
seconds_valid: 60,
|
||||||
|
@ -87,6 +91,7 @@
|
||||||
|
|
||||||
# Configures the endpoint
|
# Configures the endpoint
|
||||||
config :pleroma, Pleroma.Web.Endpoint,
|
config :pleroma, Pleroma.Web.Endpoint,
|
||||||
|
instrumenters: [Pleroma.Web.Endpoint.Instrumenter],
|
||||||
url: [host: "localhost"],
|
url: [host: "localhost"],
|
||||||
http: [
|
http: [
|
||||||
dispatch: [
|
dispatch: [
|
||||||
|
@ -356,7 +361,8 @@
|
||||||
config :pleroma_job_queue, :queues,
|
config :pleroma_job_queue, :queues,
|
||||||
federator_incoming: 50,
|
federator_incoming: 50,
|
||||||
federator_outgoing: 50,
|
federator_outgoing: 50,
|
||||||
mailer: 10
|
mailer: 10,
|
||||||
|
scheduled_activities: 10
|
||||||
|
|
||||||
config :pleroma, :fetch_initial_posts,
|
config :pleroma, :fetch_initial_posts,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
@ -401,6 +407,13 @@
|
||||||
|
|
||||||
config :pleroma, Pleroma.Mailer, adapter: Swoosh.Adapters.Sendmail
|
config :pleroma, Pleroma.Mailer, adapter: Swoosh.Adapters.Sendmail
|
||||||
|
|
||||||
|
config :prometheus, Pleroma.Web.Endpoint.MetricsExporter, path: "/api/pleroma/app_metrics"
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.ScheduledActivity,
|
||||||
|
daily_user_limit: 25,
|
||||||
|
total_user_limit: 300,
|
||||||
|
enabled: true
|
||||||
|
|
||||||
# Import environment specific config. This must remain at the bottom
|
# Import environment specific config. This must remain at the bottom
|
||||||
# of this file so it overrides the configuration defined above.
|
# of this file so it overrides the configuration defined above.
|
||||||
import_config "#{Mix.env()}.exs"
|
import_config "#{Mix.env()}.exs"
|
||||||
|
|
|
@ -50,6 +50,11 @@
|
||||||
|
|
||||||
config :pleroma_job_queue, disabled: true
|
config :pleroma_job_queue, disabled: true
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.ScheduledActivity,
|
||||||
|
daily_user_limit: 2,
|
||||||
|
total_user_limit: 3,
|
||||||
|
enabled: false
|
||||||
|
|
||||||
try do
|
try do
|
||||||
import_config "test.secret.exs"
|
import_config "test.secret.exs"
|
||||||
rescue
|
rescue
|
||||||
|
|
|
@ -58,6 +58,26 @@ Authentication is required and the user must be an admin.
|
||||||
- `password`
|
- `password`
|
||||||
- Response: User’s nickname
|
- Response: User’s nickname
|
||||||
|
|
||||||
|
## `/api/pleroma/admin/user/follow`
|
||||||
|
### Make a user follow another user
|
||||||
|
|
||||||
|
- Methods: `POST`
|
||||||
|
- Params:
|
||||||
|
- `follower`: The nickname of the follower
|
||||||
|
- `followed`: The nickname of the followed
|
||||||
|
- Response:
|
||||||
|
- "ok"
|
||||||
|
|
||||||
|
## `/api/pleroma/admin/user/unfollow`
|
||||||
|
### Make a user unfollow another user
|
||||||
|
|
||||||
|
- Methods: `POST`
|
||||||
|
- Params:
|
||||||
|
- `follower`: The nickname of the follower
|
||||||
|
- `followed`: The nickname of the followed
|
||||||
|
- Response:
|
||||||
|
- "ok"
|
||||||
|
|
||||||
## `/api/pleroma/admin/users/:nickname/toggle_activation`
|
## `/api/pleroma/admin/users/:nickname/toggle_activation`
|
||||||
|
|
||||||
### Toggle user activation
|
### Toggle user activation
|
||||||
|
|
22
docs/api/prometheus.md
Normal file
22
docs/api/prometheus.md
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# Prometheus Metrics
|
||||||
|
|
||||||
|
Pleroma includes support for exporting metrics via the [prometheus_ex](https://github.com/deadtrickster/prometheus.ex) library.
|
||||||
|
|
||||||
|
## `/api/pleroma/app_metrics`
|
||||||
|
### Exports Prometheus application metrics
|
||||||
|
* Method: `GET`
|
||||||
|
* Authentication: not required
|
||||||
|
* Params: none
|
||||||
|
* Response: JSON
|
||||||
|
|
||||||
|
## Grafana
|
||||||
|
### Config example
|
||||||
|
The following is a config example to use with [Grafana](https://grafana.com)
|
||||||
|
|
||||||
|
```
|
||||||
|
- job_name: 'beam'
|
||||||
|
metrics_path: /api/pleroma/app_metrics
|
||||||
|
scheme: https
|
||||||
|
static_configs:
|
||||||
|
- targets: ['pleroma.soykaf.com']
|
||||||
|
```
|
|
@ -317,6 +317,7 @@ Pleroma has the following queues:
|
||||||
* `federator_outgoing` - Outgoing federation
|
* `federator_outgoing` - Outgoing federation
|
||||||
* `federator_incoming` - Incoming federation
|
* `federator_incoming` - Incoming federation
|
||||||
* `mailer` - Email sender, see [`Pleroma.Mailer`](#pleroma-mailer)
|
* `mailer` - Email sender, see [`Pleroma.Mailer`](#pleroma-mailer)
|
||||||
|
* `scheduled_activities` - Scheduled activities, see [`Pleroma.ScheduledActivities`](#pleromascheduledactivity)
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
@ -390,6 +391,12 @@ config :auto_linker,
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Pleroma.ScheduledActivity
|
||||||
|
|
||||||
|
* `daily_user_limit`: the number of scheduled activities a user is allowed to create in a single day (Default: `25`)
|
||||||
|
* `total_user_limit`: the number of scheduled activities a user is allowed to create in total (Default: `300`)
|
||||||
|
* `enabled`: whether scheduled activities are sent to the job queue to be executed
|
||||||
|
|
||||||
## Pleroma.Web.Auth.Authenticator
|
## Pleroma.Web.Auth.Authenticator
|
||||||
|
|
||||||
* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator
|
* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator
|
||||||
|
|
|
@ -31,7 +31,7 @@ defmodule Pleroma.Activity do
|
||||||
field(:data, :map)
|
field(:data, :map)
|
||||||
field(:local, :boolean, default: true)
|
field(:local, :boolean, default: true)
|
||||||
field(:actor, :string)
|
field(:actor, :string)
|
||||||
field(:recipients, {:array, :string})
|
field(:recipients, {:array, :string}, default: [])
|
||||||
has_many(:notifications, Notification, on_delete: :delete_all)
|
has_many(:notifications, Notification, on_delete: :delete_all)
|
||||||
|
|
||||||
# Attention: this is a fake relation, don't try to preload it blindly and expect it to work!
|
# Attention: this is a fake relation, don't try to preload it blindly and expect it to work!
|
||||||
|
|
|
@ -25,6 +25,7 @@ def start(_type, _args) do
|
||||||
import Cachex.Spec
|
import Cachex.Spec
|
||||||
|
|
||||||
Pleroma.Config.DeprecationWarnings.warn()
|
Pleroma.Config.DeprecationWarnings.warn()
|
||||||
|
setup_instrumenters()
|
||||||
|
|
||||||
# Define workers and child supervisors to be supervised
|
# Define workers and child supervisors to be supervised
|
||||||
children =
|
children =
|
||||||
|
@ -103,7 +104,8 @@ def start(_type, _args) do
|
||||||
],
|
],
|
||||||
id: :cachex_idem
|
id: :cachex_idem
|
||||||
),
|
),
|
||||||
worker(Pleroma.FlakeId, [])
|
worker(Pleroma.FlakeId, []),
|
||||||
|
worker(Pleroma.ScheduledActivityWorker, [])
|
||||||
] ++
|
] ++
|
||||||
hackney_pool_children() ++
|
hackney_pool_children() ++
|
||||||
[
|
[
|
||||||
|
@ -126,6 +128,24 @@ def start(_type, _args) do
|
||||||
Supervisor.start_link(children, opts)
|
Supervisor.start_link(children, opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp setup_instrumenters do
|
||||||
|
require Prometheus.Registry
|
||||||
|
|
||||||
|
:ok =
|
||||||
|
:telemetry.attach(
|
||||||
|
"prometheus-ecto",
|
||||||
|
[:pleroma, :repo, :query],
|
||||||
|
&Pleroma.Repo.Instrumenter.handle_event/4,
|
||||||
|
%{}
|
||||||
|
)
|
||||||
|
|
||||||
|
Prometheus.Registry.register_collector(:prometheus_process_collector)
|
||||||
|
Pleroma.Web.Endpoint.MetricsExporter.setup()
|
||||||
|
Pleroma.Web.Endpoint.PipelineInstrumenter.setup()
|
||||||
|
Pleroma.Web.Endpoint.Instrumenter.setup()
|
||||||
|
Pleroma.Repo.Instrumenter.setup()
|
||||||
|
end
|
||||||
|
|
||||||
def enabled_hackney_pools do
|
def enabled_hackney_pools do
|
||||||
[:media] ++
|
[:media] ++
|
||||||
if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
|
if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
|
||||||
|
|
|
@ -28,21 +28,20 @@ def filter_tags(html, scrubber), do: Scrubber.scrub(html, scrubber)
|
||||||
def filter_tags(html), do: filter_tags(html, nil)
|
def filter_tags(html), do: filter_tags(html, nil)
|
||||||
def strip_tags(html), do: Scrubber.scrub(html, Scrubber.StripTags)
|
def strip_tags(html), do: Scrubber.scrub(html, Scrubber.StripTags)
|
||||||
|
|
||||||
# TODO: rename object to activity because that's what it is really working with
|
def get_cached_scrubbed_html_for_activity(content, scrubbers, activity, key \\ "") do
|
||||||
def get_cached_scrubbed_html_for_object(content, scrubbers, object, module) do
|
key = "#{key}#{generate_scrubber_signature(scrubbers)}|#{activity.id}"
|
||||||
key = "#{module}#{generate_scrubber_signature(scrubbers)}|#{object.id}"
|
|
||||||
|
|
||||||
Cachex.fetch!(:scrubber_cache, key, fn _key ->
|
Cachex.fetch!(:scrubber_cache, key, fn _key ->
|
||||||
ensure_scrubbed_html(content, scrubbers, object.data["object"]["fake"] || false)
|
ensure_scrubbed_html(content, scrubbers, activity.data["object"]["fake"] || false)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_cached_stripped_html_for_object(content, object, module) do
|
def get_cached_stripped_html_for_activity(content, activity, key) do
|
||||||
get_cached_scrubbed_html_for_object(
|
get_cached_scrubbed_html_for_activity(
|
||||||
content,
|
content,
|
||||||
HtmlSanitizeEx.Scrubber.StripTags,
|
HtmlSanitizeEx.Scrubber.StripTags,
|
||||||
object,
|
activity,
|
||||||
module
|
key
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,10 @@ defmodule Pleroma.Repo do
|
||||||
adapter: Ecto.Adapters.Postgres,
|
adapter: Ecto.Adapters.Postgres,
|
||||||
migration_timestamps: [type: :naive_datetime_usec]
|
migration_timestamps: [type: :naive_datetime_usec]
|
||||||
|
|
||||||
|
defmodule Instrumenter do
|
||||||
|
use Prometheus.EctoInstrumenter
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Dynamically loads the repository url from the
|
Dynamically loads the repository url from the
|
||||||
DATABASE_URL environment variable.
|
DATABASE_URL environment variable.
|
||||||
|
|
161
lib/pleroma/scheduled_activity.ex
Normal file
161
lib/pleroma/scheduled_activity.ex
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.ScheduledActivity do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
|
|
||||||
|
import Ecto.Query
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@min_offset :timer.minutes(5)
|
||||||
|
|
||||||
|
schema "scheduled_activities" do
|
||||||
|
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||||
|
field(:scheduled_at, :naive_datetime)
|
||||||
|
field(:params, :map)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(%ScheduledActivity{} = scheduled_activity, attrs) do
|
||||||
|
scheduled_activity
|
||||||
|
|> cast(attrs, [:scheduled_at, :params])
|
||||||
|
|> validate_required([:scheduled_at, :params])
|
||||||
|
|> validate_scheduled_at()
|
||||||
|
|> with_media_attachments()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp with_media_attachments(
|
||||||
|
%{changes: %{params: %{"media_ids" => media_ids} = params}} = changeset
|
||||||
|
)
|
||||||
|
when is_list(media_ids) do
|
||||||
|
media_attachments = Utils.attachments_from_ids(%{"media_ids" => media_ids})
|
||||||
|
|
||||||
|
params =
|
||||||
|
params
|
||||||
|
|> Map.put("media_attachments", media_attachments)
|
||||||
|
|> Map.put("media_ids", media_ids)
|
||||||
|
|
||||||
|
put_change(changeset, :params, params)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp with_media_attachments(changeset), do: changeset
|
||||||
|
|
||||||
|
def update_changeset(%ScheduledActivity{} = scheduled_activity, attrs) do
|
||||||
|
scheduled_activity
|
||||||
|
|> cast(attrs, [:scheduled_at])
|
||||||
|
|> validate_required([:scheduled_at])
|
||||||
|
|> validate_scheduled_at()
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_scheduled_at(changeset) do
|
||||||
|
validate_change(changeset, :scheduled_at, fn _, scheduled_at ->
|
||||||
|
cond do
|
||||||
|
not far_enough?(scheduled_at) ->
|
||||||
|
[scheduled_at: "must be at least 5 minutes from now"]
|
||||||
|
|
||||||
|
exceeds_daily_user_limit?(changeset.data.user_id, scheduled_at) ->
|
||||||
|
[scheduled_at: "daily limit exceeded"]
|
||||||
|
|
||||||
|
exceeds_total_user_limit?(changeset.data.user_id) ->
|
||||||
|
[scheduled_at: "total limit exceeded"]
|
||||||
|
|
||||||
|
true ->
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def exceeds_daily_user_limit?(user_id, scheduled_at) do
|
||||||
|
ScheduledActivity
|
||||||
|
|> where(user_id: ^user_id)
|
||||||
|
|> where([sa], type(sa.scheduled_at, :date) == type(^scheduled_at, :date))
|
||||||
|
|> select([sa], count(sa.id))
|
||||||
|
|> Repo.one()
|
||||||
|
|> Kernel.>=(Config.get([ScheduledActivity, :daily_user_limit]))
|
||||||
|
end
|
||||||
|
|
||||||
|
def exceeds_total_user_limit?(user_id) do
|
||||||
|
ScheduledActivity
|
||||||
|
|> where(user_id: ^user_id)
|
||||||
|
|> select([sa], count(sa.id))
|
||||||
|
|> Repo.one()
|
||||||
|
|> Kernel.>=(Config.get([ScheduledActivity, :total_user_limit]))
|
||||||
|
end
|
||||||
|
|
||||||
|
def far_enough?(scheduled_at) when is_binary(scheduled_at) do
|
||||||
|
with {:ok, scheduled_at} <- Ecto.Type.cast(:naive_datetime, scheduled_at) do
|
||||||
|
far_enough?(scheduled_at)
|
||||||
|
else
|
||||||
|
_ -> false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def far_enough?(scheduled_at) do
|
||||||
|
now = NaiveDateTime.utc_now()
|
||||||
|
diff = NaiveDateTime.diff(scheduled_at, now, :millisecond)
|
||||||
|
diff > @min_offset
|
||||||
|
end
|
||||||
|
|
||||||
|
def new(%User{} = user, attrs) do
|
||||||
|
%ScheduledActivity{user_id: user.id}
|
||||||
|
|> changeset(attrs)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(%User{} = user, attrs) do
|
||||||
|
user
|
||||||
|
|> new(attrs)
|
||||||
|
|> Repo.insert()
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(%User{} = user, scheduled_activity_id) do
|
||||||
|
ScheduledActivity
|
||||||
|
|> where(user_id: ^user.id)
|
||||||
|
|> where(id: ^scheduled_activity_id)
|
||||||
|
|> Repo.one()
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(%ScheduledActivity{} = scheduled_activity, attrs) do
|
||||||
|
scheduled_activity
|
||||||
|
|> update_changeset(attrs)
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(%ScheduledActivity{} = scheduled_activity) do
|
||||||
|
scheduled_activity
|
||||||
|
|> Repo.delete()
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(id) when is_binary(id) or is_integer(id) do
|
||||||
|
ScheduledActivity
|
||||||
|
|> where(id: ^id)
|
||||||
|
|> select([sa], sa)
|
||||||
|
|> Repo.delete_all()
|
||||||
|
|> case do
|
||||||
|
{1, [scheduled_activity]} -> {:ok, scheduled_activity}
|
||||||
|
_ -> :error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def for_user_query(%User{} = user) do
|
||||||
|
ScheduledActivity
|
||||||
|
|> where(user_id: ^user.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def due_activities(offset \\ 0) do
|
||||||
|
naive_datetime =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(offset, :millisecond)
|
||||||
|
|
||||||
|
ScheduledActivity
|
||||||
|
|> where([sa], sa.scheduled_at < ^naive_datetime)
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
end
|
58
lib/pleroma/scheduled_activity_worker.ex
Normal file
58
lib/pleroma/scheduled_activity_worker.ex
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.ScheduledActivityWorker do
|
||||||
|
@moduledoc """
|
||||||
|
Sends scheduled activities to the job queue.
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
use GenServer
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@schedule_interval :timer.minutes(1)
|
||||||
|
|
||||||
|
def start_link do
|
||||||
|
GenServer.start_link(__MODULE__, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
def init(_) do
|
||||||
|
if Config.get([ScheduledActivity, :enabled]) do
|
||||||
|
schedule_next()
|
||||||
|
{:ok, nil}
|
||||||
|
else
|
||||||
|
:ignore
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform(:execute, scheduled_activity_id) do
|
||||||
|
try do
|
||||||
|
{:ok, scheduled_activity} = ScheduledActivity.delete(scheduled_activity_id)
|
||||||
|
%User{} = user = User.get_cached_by_id(scheduled_activity.user_id)
|
||||||
|
{:ok, _result} = CommonAPI.post(user, scheduled_activity.params)
|
||||||
|
rescue
|
||||||
|
error ->
|
||||||
|
Logger.error(
|
||||||
|
"#{__MODULE__} Couldn't create a status from the scheduled activity: #{inspect(error)}"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info(:perform, state) do
|
||||||
|
ScheduledActivity.due_activities(@schedule_interval)
|
||||||
|
|> Enum.each(fn scheduled_activity ->
|
||||||
|
PleromaJobQueue.enqueue(:scheduled_activities, __MODULE__, [:execute, scheduled_activity.id])
|
||||||
|
end)
|
||||||
|
|
||||||
|
schedule_next()
|
||||||
|
{:noreply, state}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp schedule_next do
|
||||||
|
Process.send_after(self(), :perform, @schedule_interval)
|
||||||
|
end
|
||||||
|
end
|
|
@ -25,6 +25,26 @@ def user_delete(conn, %{"nickname" => nickname}) do
|
||||||
|> json(nickname)
|
|> json(nickname)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def user_follow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do
|
||||||
|
with %User{} = follower <- User.get_by_nickname(follower_nick),
|
||||||
|
%User{} = followed <- User.get_by_nickname(followed_nick) do
|
||||||
|
User.follow(follower, followed)
|
||||||
|
end
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> json("ok")
|
||||||
|
end
|
||||||
|
|
||||||
|
def user_unfollow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do
|
||||||
|
with %User{} = follower <- User.get_by_nickname(follower_nick),
|
||||||
|
%User{} = followed <- User.get_by_nickname(followed_nick) do
|
||||||
|
User.unfollow(follower, followed)
|
||||||
|
end
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> json("ok")
|
||||||
|
end
|
||||||
|
|
||||||
def user_create(
|
def user_create(
|
||||||
conn,
|
conn,
|
||||||
%{"nickname" => nickname, "email" => email, "password" => password}
|
%{"nickname" => nickname, "email" => email, "password" => password}
|
||||||
|
|
|
@ -80,6 +80,26 @@ defmodule Pleroma.Web.Endpoint do
|
||||||
extra: same_site
|
extra: same_site
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Note: the plug and its configuration is compile-time this can't be upstreamed yet
|
||||||
|
if proxies = Pleroma.Config.get([__MODULE__, :reverse_proxies]) do
|
||||||
|
plug(RemoteIp, proxies: proxies)
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule Instrumenter do
|
||||||
|
use Prometheus.PhoenixInstrumenter
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule PipelineInstrumenter do
|
||||||
|
use Prometheus.PlugPipelineInstrumenter
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule MetricsExporter do
|
||||||
|
use Prometheus.PlugExporter
|
||||||
|
end
|
||||||
|
|
||||||
|
plug(PipelineInstrumenter)
|
||||||
|
plug(MetricsExporter)
|
||||||
|
|
||||||
plug(Pleroma.Web.Router)
|
plug(Pleroma.Web.Router)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
|
@ -5,6 +5,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Pagination
|
alias Pleroma.Pagination
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
def get_followers(user, params \\ %{}) do
|
def get_followers(user, params \\ %{}) do
|
||||||
|
@ -28,6 +29,12 @@ def get_notifications(user, params \\ %{}) do
|
||||||
|> Pagination.fetch_paginated(params)
|
|> Pagination.fetch_paginated(params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_scheduled_activities(user, params \\ %{}) do
|
||||||
|
user
|
||||||
|
|> ScheduledActivity.for_user_query()
|
||||||
|
|> Pagination.fetch_paginated(params)
|
||||||
|
end
|
||||||
|
|
||||||
defp cast_params(params) do
|
defp cast_params(params) do
|
||||||
param_types = %{
|
param_types = %{
|
||||||
exclude_types: {:array, :string}
|
exclude_types: {:array, :string}
|
||||||
|
|
|
@ -5,12 +5,14 @@
|
||||||
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Ecto.Changeset
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
alias Pleroma.Filter
|
alias Pleroma.Filter
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
alias Pleroma.Stats
|
alias Pleroma.Stats
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web
|
alias Pleroma.Web
|
||||||
|
@ -25,6 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||||
alias Pleroma.Web.MastodonAPI.MastodonView
|
alias Pleroma.Web.MastodonAPI.MastodonView
|
||||||
alias Pleroma.Web.MastodonAPI.NotificationView
|
alias Pleroma.Web.MastodonAPI.NotificationView
|
||||||
alias Pleroma.Web.MastodonAPI.ReportView
|
alias Pleroma.Web.MastodonAPI.ReportView
|
||||||
|
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
alias Pleroma.Web.MediaProxy
|
alias Pleroma.Web.MediaProxy
|
||||||
alias Pleroma.Web.OAuth.App
|
alias Pleroma.Web.OAuth.App
|
||||||
|
@ -364,6 +367,55 @@ def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def scheduled_statuses(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do
|
||||||
|
conn
|
||||||
|
|> add_link_headers(:scheduled_statuses, scheduled_activities)
|
||||||
|
|> put_view(ScheduledActivityView)
|
||||||
|
|> render("index.json", %{scheduled_activities: scheduled_activities})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def show_scheduled_status(%{assigns: %{user: user}} = conn, %{"id" => scheduled_activity_id}) do
|
||||||
|
with %ScheduledActivity{} = scheduled_activity <-
|
||||||
|
ScheduledActivity.get(user, scheduled_activity_id) do
|
||||||
|
conn
|
||||||
|
|> put_view(ScheduledActivityView)
|
||||||
|
|> render("show.json", %{scheduled_activity: scheduled_activity})
|
||||||
|
else
|
||||||
|
_ -> {:error, :not_found}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_scheduled_status(
|
||||||
|
%{assigns: %{user: user}} = conn,
|
||||||
|
%{"id" => scheduled_activity_id} = params
|
||||||
|
) do
|
||||||
|
with %ScheduledActivity{} = scheduled_activity <-
|
||||||
|
ScheduledActivity.get(user, scheduled_activity_id),
|
||||||
|
{:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do
|
||||||
|
conn
|
||||||
|
|> put_view(ScheduledActivityView)
|
||||||
|
|> render("show.json", %{scheduled_activity: scheduled_activity})
|
||||||
|
else
|
||||||
|
nil -> {:error, :not_found}
|
||||||
|
error -> error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_scheduled_status(%{assigns: %{user: user}} = conn, %{"id" => scheduled_activity_id}) do
|
||||||
|
with %ScheduledActivity{} = scheduled_activity <-
|
||||||
|
ScheduledActivity.get(user, scheduled_activity_id),
|
||||||
|
{:ok, scheduled_activity} <- ScheduledActivity.delete(scheduled_activity) do
|
||||||
|
conn
|
||||||
|
|> put_view(ScheduledActivityView)
|
||||||
|
|> render("show.json", %{scheduled_activity: scheduled_activity})
|
||||||
|
else
|
||||||
|
nil -> {:error, :not_found}
|
||||||
|
error -> error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def post_status(conn, %{"status" => "", "media_ids" => media_ids} = params)
|
def post_status(conn, %{"status" => "", "media_ids" => media_ids} = params)
|
||||||
when length(media_ids) > 0 do
|
when length(media_ids) > 0 do
|
||||||
params =
|
params =
|
||||||
|
@ -384,13 +436,28 @@ def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
|
||||||
_ -> Ecto.UUID.generate()
|
_ -> Ecto.UUID.generate()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
scheduled_at = params["scheduled_at"]
|
||||||
|
|
||||||
|
if scheduled_at && ScheduledActivity.far_enough?(scheduled_at) do
|
||||||
|
with {:ok, scheduled_activity} <-
|
||||||
|
ScheduledActivity.create(user, %{"params" => params, "scheduled_at" => scheduled_at}) do
|
||||||
|
conn
|
||||||
|
|> put_view(ScheduledActivityView)
|
||||||
|
|> render("show.json", %{scheduled_activity: scheduled_activity})
|
||||||
|
end
|
||||||
|
else
|
||||||
|
params = Map.drop(params, ["scheduled_at"])
|
||||||
|
|
||||||
{:ok, activity} =
|
{:ok, activity} =
|
||||||
Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ -> CommonAPI.post(user, params) end)
|
Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ ->
|
||||||
|
CommonAPI.post(user, params)
|
||||||
|
end)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
|
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
|
||||||
|
@ -1091,9 +1158,7 @@ def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params)
|
||||||
end
|
end
|
||||||
|
|
||||||
def index(%{assigns: %{user: user}} = conn, _params) do
|
def index(%{assigns: %{user: user}} = conn, _params) do
|
||||||
token =
|
token = get_session(conn, :oauth_token)
|
||||||
conn
|
|
||||||
|> get_session(:oauth_token)
|
|
||||||
|
|
||||||
if user && token do
|
if user && token do
|
||||||
mastodon_emoji = mastodonized_emoji()
|
mastodon_emoji = mastodonized_emoji()
|
||||||
|
@ -1194,6 +1259,7 @@ def index(%{assigns: %{user: user}} = conn, _params) do
|
||||||
|> render("index.html", %{initial_state: initial_state, flavour: flavour})
|
|> render("index.html", %{initial_state: initial_state, flavour: flavour})
|
||||||
else
|
else
|
||||||
conn
|
conn
|
||||||
|
|> put_session(:return_to, conn.request_path)
|
||||||
|> redirect(to: "/web/login")
|
|> redirect(to: "/web/login")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1278,12 +1344,20 @@ def login(conn, _) do
|
||||||
scope: Enum.join(app.scopes, " ")
|
scope: Enum.join(app.scopes, " ")
|
||||||
)
|
)
|
||||||
|
|
||||||
conn
|
redirect(conn, to: path)
|
||||||
|> redirect(to: path)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp local_mastodon_root_path(conn), do: mastodon_api_path(conn, :index, ["getting-started"])
|
defp local_mastodon_root_path(conn) do
|
||||||
|
case get_session(conn, :return_to) do
|
||||||
|
nil ->
|
||||||
|
mastodon_api_path(conn, :index, ["getting-started"])
|
||||||
|
|
||||||
|
return_to ->
|
||||||
|
delete_session(conn, :return_to)
|
||||||
|
return_to
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp get_or_make_app do
|
defp get_or_make_app do
|
||||||
find_attrs = %{client_name: @local_mastodon_name, redirect_uris: "."}
|
find_attrs = %{client_name: @local_mastodon_name, redirect_uris: "."}
|
||||||
|
@ -1399,6 +1473,23 @@ def delete_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
|
||||||
|
|
||||||
# fallback action
|
# fallback action
|
||||||
#
|
#
|
||||||
|
def errors(conn, {:error, %Changeset{} = changeset}) do
|
||||||
|
error_message =
|
||||||
|
changeset
|
||||||
|
|> Changeset.traverse_errors(fn {message, _opt} -> message end)
|
||||||
|
|> Enum.map_join(", ", fn {_k, v} -> v end)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_status(422)
|
||||||
|
|> json(%{error: error_message})
|
||||||
|
end
|
||||||
|
|
||||||
|
def errors(conn, {:error, :not_found}) do
|
||||||
|
conn
|
||||||
|
|> put_status(404)
|
||||||
|
|> json(%{error: "Record not found"})
|
||||||
|
end
|
||||||
|
|
||||||
def errors(conn, _) do
|
def errors(conn, _) do
|
||||||
conn
|
conn
|
||||||
|> put_status(500)
|
|> put_status(500)
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.MastodonAPI.ScheduledActivityView do
|
||||||
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
|
||||||
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
|
||||||
|
def render("index.json", %{scheduled_activities: scheduled_activities}) do
|
||||||
|
render_many(scheduled_activities, ScheduledActivityView, "show.json")
|
||||||
|
end
|
||||||
|
|
||||||
|
def render("show.json", %{scheduled_activity: %ScheduledActivity{} = scheduled_activity}) do
|
||||||
|
%{
|
||||||
|
id: to_string(scheduled_activity.id),
|
||||||
|
scheduled_at: CommonAPI.Utils.to_masto_date(scheduled_activity.scheduled_at),
|
||||||
|
params: status_params(scheduled_activity.params)
|
||||||
|
}
|
||||||
|
|> with_media_attachments(scheduled_activity)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp with_media_attachments(data, %{params: %{"media_attachments" => media_attachments}}) do
|
||||||
|
try do
|
||||||
|
attachments = render_many(media_attachments, StatusView, "attachment.json", as: :attachment)
|
||||||
|
Map.put(data, :media_attachments, attachments)
|
||||||
|
rescue
|
||||||
|
_ -> data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp with_media_attachments(data, _), do: data
|
||||||
|
|
||||||
|
defp status_params(params) do
|
||||||
|
data = %{
|
||||||
|
text: params["status"],
|
||||||
|
sensitive: params["sensitive"],
|
||||||
|
spoiler_text: params["spoiler_text"],
|
||||||
|
visibility: params["visibility"],
|
||||||
|
scheduled_at: params["scheduled_at"],
|
||||||
|
poll: params["poll"],
|
||||||
|
in_reply_to_id: params["in_reply_to_id"]
|
||||||
|
}
|
||||||
|
|
||||||
|
data =
|
||||||
|
if media_ids = params["media_ids"] do
|
||||||
|
Map.put(data, :media_ids, media_ids)
|
||||||
|
else
|
||||||
|
data
|
||||||
|
end
|
||||||
|
|
||||||
|
data
|
||||||
|
end
|
||||||
|
end
|
|
@ -147,10 +147,18 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
|
||||||
content =
|
content =
|
||||||
object
|
object
|
||||||
|> render_content()
|
|> render_content()
|
||||||
|> HTML.get_cached_scrubbed_html_for_object(
|
|> HTML.get_cached_scrubbed_html_for_activity(
|
||||||
User.html_filter_policy(opts[:for]),
|
User.html_filter_policy(opts[:for]),
|
||||||
activity,
|
activity,
|
||||||
__MODULE__
|
"mastoapi:content"
|
||||||
|
)
|
||||||
|
|
||||||
|
summary =
|
||||||
|
(object["summary"] || "")
|
||||||
|
|> HTML.get_cached_scrubbed_html_for_activity(
|
||||||
|
User.html_filter_policy(opts[:for]),
|
||||||
|
activity,
|
||||||
|
"mastoapi:summary"
|
||||||
)
|
)
|
||||||
|
|
||||||
card = render("card.json", Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity))
|
card = render("card.json", Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity))
|
||||||
|
@ -182,7 +190,7 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
|
||||||
muted: CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user),
|
muted: CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user),
|
||||||
pinned: pinned?(activity, user),
|
pinned: pinned?(activity, user),
|
||||||
sensitive: sensitive,
|
sensitive: sensitive,
|
||||||
spoiler_text: object["summary"] || "",
|
spoiler_text: summary,
|
||||||
visibility: get_visibility(object),
|
visibility: get_visibility(object),
|
||||||
media_attachments: attachments,
|
media_attachments: attachments,
|
||||||
mentions: mentions,
|
mentions: mentions,
|
||||||
|
|
|
@ -12,7 +12,7 @@ def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do
|
||||||
# html content comes from DB already encoded, decode first and scrub after
|
# html content comes from DB already encoded, decode first and scrub after
|
||||||
|> HtmlEntities.decode()
|
|> HtmlEntities.decode()
|
||||||
|> String.replace(~r/<br\s?\/?>/, " ")
|
|> String.replace(~r/<br\s?\/?>/, " ")
|
||||||
|> HTML.get_cached_stripped_html_for_object(object, __MODULE__)
|
|> HTML.get_cached_stripped_html_for_activity(object, "metadata")
|
||||||
|> Formatter.demojify()
|
|> Formatter.demojify()
|
||||||
|> Formatter.truncate()
|
|> Formatter.truncate()
|
||||||
end
|
end
|
||||||
|
|
|
@ -154,6 +154,7 @@ def token_exchange(
|
||||||
with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn, params)},
|
with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn, params)},
|
||||||
%App{} = app <- get_app_from_request(conn, params),
|
%App{} = app <- get_app_from_request(conn, params),
|
||||||
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
|
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
|
||||||
|
{:user_active, true} <- {:user_active, !user.info.deactivated},
|
||||||
scopes <- oauth_scopes(params, app.scopes),
|
scopes <- oauth_scopes(params, app.scopes),
|
||||||
[] <- scopes -- app.scopes,
|
[] <- scopes -- app.scopes,
|
||||||
true <- Enum.any?(scopes),
|
true <- Enum.any?(scopes),
|
||||||
|
@ -177,6 +178,11 @@ def token_exchange(
|
||||||
|> put_status(:forbidden)
|
|> put_status(:forbidden)
|
||||||
|> json(%{error: "Your login is missing a confirmed e-mail address"})
|
|> json(%{error: "Your login is missing a confirmed e-mail address"})
|
||||||
|
|
||||||
|
{:user_active, false} ->
|
||||||
|
conn
|
||||||
|
|> put_status(:forbidden)
|
||||||
|
|> json(%{error: "Your account is currently disabled"})
|
||||||
|
|
||||||
_error ->
|
_error ->
|
||||||
put_status(conn, 400)
|
put_status(conn, 400)
|
||||||
|> json(%{error: "Invalid credentials"})
|
|> json(%{error: "Invalid credentials"})
|
||||||
|
|
|
@ -145,8 +145,12 @@ defmodule Pleroma.Web.Router do
|
||||||
scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
|
scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
|
||||||
pipe_through([:admin_api, :oauth_write])
|
pipe_through([:admin_api, :oauth_write])
|
||||||
|
|
||||||
|
post("/user/follow", AdminAPIController, :user_follow)
|
||||||
|
post("/user/unfollow", AdminAPIController, :user_unfollow)
|
||||||
|
|
||||||
get("/users", AdminAPIController, :list_users)
|
get("/users", AdminAPIController, :list_users)
|
||||||
get("/users/:nickname", AdminAPIController, :user_show)
|
get("/users/:nickname", AdminAPIController, :user_show)
|
||||||
|
|
||||||
delete("/user", AdminAPIController, :user_delete)
|
delete("/user", AdminAPIController, :user_delete)
|
||||||
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
|
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
|
||||||
post("/user", AdminAPIController, :user_create)
|
post("/user", AdminAPIController, :user_create)
|
||||||
|
@ -255,6 +259,9 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/notifications", MastodonAPIController, :notifications)
|
get("/notifications", MastodonAPIController, :notifications)
|
||||||
get("/notifications/:id", MastodonAPIController, :get_notification)
|
get("/notifications/:id", MastodonAPIController, :get_notification)
|
||||||
|
|
||||||
|
get("/scheduled_statuses", MastodonAPIController, :scheduled_statuses)
|
||||||
|
get("/scheduled_statuses/:id", MastodonAPIController, :show_scheduled_status)
|
||||||
|
|
||||||
get("/lists", MastodonAPIController, :get_lists)
|
get("/lists", MastodonAPIController, :get_lists)
|
||||||
get("/lists/:id", MastodonAPIController, :get_list)
|
get("/lists/:id", MastodonAPIController, :get_list)
|
||||||
get("/lists/:id/accounts", MastodonAPIController, :list_accounts)
|
get("/lists/:id/accounts", MastodonAPIController, :list_accounts)
|
||||||
|
@ -289,6 +296,9 @@ defmodule Pleroma.Web.Router do
|
||||||
post("/statuses/:id/mute", MastodonAPIController, :mute_conversation)
|
post("/statuses/:id/mute", MastodonAPIController, :mute_conversation)
|
||||||
post("/statuses/:id/unmute", MastodonAPIController, :unmute_conversation)
|
post("/statuses/:id/unmute", MastodonAPIController, :unmute_conversation)
|
||||||
|
|
||||||
|
put("/scheduled_statuses/:id", MastodonAPIController, :update_scheduled_status)
|
||||||
|
delete("/scheduled_statuses/:id", MastodonAPIController, :delete_scheduled_status)
|
||||||
|
|
||||||
post("/media", MastodonAPIController, :upload)
|
post("/media", MastodonAPIController, :upload)
|
||||||
put("/media/:id", MastodonAPIController, :update_media)
|
put("/media/:id", MastodonAPIController, :update_media)
|
||||||
|
|
||||||
|
|
|
@ -254,10 +254,10 @@ def render(
|
||||||
|
|
||||||
html =
|
html =
|
||||||
content
|
content
|
||||||
|> HTML.get_cached_scrubbed_html_for_object(
|
|> HTML.get_cached_scrubbed_html_for_activity(
|
||||||
User.html_filter_policy(opts[:for]),
|
User.html_filter_policy(opts[:for]),
|
||||||
activity,
|
activity,
|
||||||
__MODULE__
|
"twitterapi:content"
|
||||||
)
|
)
|
||||||
|> Formatter.emojify(object["emoji"])
|
|> Formatter.emojify(object["emoji"])
|
||||||
|
|
||||||
|
@ -265,7 +265,7 @@ def render(
|
||||||
if content do
|
if content do
|
||||||
content
|
content
|
||||||
|> String.replace(~r/<br\s?\/?>/, "\n")
|
|> String.replace(~r/<br\s?\/?>/, "\n")
|
||||||
|> HTML.get_cached_stripped_html_for_object(activity, __MODULE__)
|
|> HTML.get_cached_stripped_html_for_activity(activity, "twitterapi:content")
|
||||||
else
|
else
|
||||||
""
|
""
|
||||||
end
|
end
|
||||||
|
|
7
mix.exs
7
mix.exs
|
@ -103,6 +103,13 @@ defp deps do
|
||||||
git: "https://git.pleroma.social/pleroma/auto_linker.git",
|
git: "https://git.pleroma.social/pleroma/auto_linker.git",
|
||||||
ref: "479dd343f4e563ff91215c8275f3b5c67e032850"},
|
ref: "479dd343f4e563ff91215c8275f3b5c67e032850"},
|
||||||
{:pleroma_job_queue, "~> 0.2.0"},
|
{:pleroma_job_queue, "~> 0.2.0"},
|
||||||
|
{:telemetry, "~> 0.3"},
|
||||||
|
{:prometheus_ex, "~> 3.0"},
|
||||||
|
{:prometheus_plugs, "~> 1.1"},
|
||||||
|
{:prometheus_phoenix, "~> 1.2"},
|
||||||
|
{:prometheus_ecto, "~> 1.4"},
|
||||||
|
{:prometheus_process_collector, "~> 1.4"},
|
||||||
|
{:recon, github: "ferd/recon", tag: "2.4.0"},
|
||||||
{:quack, "~> 0.1.1"}
|
{:quack, "~> 0.1.1"}
|
||||||
] ++ oauth_deps
|
] ++ oauth_deps
|
||||||
end
|
end
|
||||||
|
|
8
mix.lock
8
mix.lock
|
@ -1,4 +1,5 @@
|
||||||
%{
|
%{
|
||||||
|
"accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm"},
|
||||||
"auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "479dd343f4e563ff91215c8275f3b5c67e032850", [ref: "479dd343f4e563ff91215c8275f3b5c67e032850"]},
|
"auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "479dd343f4e563ff91215c8275f3b5c67e032850", [ref: "479dd343f4e563ff91215c8275f3b5c67e032850"]},
|
||||||
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
|
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
|
||||||
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
|
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
|
||||||
|
@ -57,8 +58,15 @@
|
||||||
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
|
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
|
||||||
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"},
|
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"},
|
||||||
"postgrex": {:hex, :postgrex, "0.14.1", "63247d4a5ad6b9de57a0bac5d807e1c32d41e39c04b8a4156a26c63bcd8a2e49", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
|
"postgrex": {:hex, :postgrex, "0.14.1", "63247d4a5ad6b9de57a0bac5d807e1c32d41e39c04b8a4156a26c63bcd8a2e49", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
|
"prometheus": {:hex, :prometheus, "4.2.2", "a830e77b79dc6d28183f4db050a7cac926a6c58f1872f9ef94a35cd989aceef8", [:mix, :rebar3], [], "hexpm"},
|
||||||
|
"prometheus_ecto": {:hex, :prometheus_ecto, "1.4.1", "6c768ea9654de871e5b32fab2eac348467b3021604ebebbcbd8bcbe806a65ed5", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
"prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
"prometheus_phoenix": {:hex, :prometheus_phoenix, "1.2.1", "964a74dfbc055f781d3a75631e06ce3816a2913976d1df7830283aa3118a797a", [:mix], [{:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
"prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
|
"prometheus_process_collector": {:hex, :prometheus_process_collector, "1.4.0", "6dbd39e3165b9ef1c94a7a820e9ffe08479f949dcdd431ed4aaea7b250eebfde", [:rebar3], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm"},
|
"quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
|
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
|
||||||
|
"recon": {:git, "https://github.com/ferd/recon.git", "75d70c7c08926d2f24f1ee6de14ee50fe8a52763", [tag: "2.4.0"]},
|
||||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
|
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
|
||||||
"swoosh": {:hex, :swoosh, "0.20.0", "9a6c13822c9815993c03b6f8fccc370fcffb3c158d9754f67b1fdee6b3a5d928", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.12", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"},
|
"swoosh": {:hex, :swoosh, "0.20.0", "9a6c13822c9815993c03b6f8fccc370fcffb3c158d9754f67b1fdee6b3a5d928", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.12", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
"syslog": {:git, "https://github.com/Vagabond/erlang-syslog.git", "4a6c6f2c996483e86c1320e9553f91d337bcb6aa", [tag: "1.0.5"]},
|
"syslog": {:git, "https://github.com/Vagabond/erlang-syslog.git", "4a6c6f2c996483e86c1320e9553f91d337bcb6aa", [tag: "1.0.5"]},
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.CreateScheduledActivities do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:scheduled_activities) do
|
||||||
|
add(:user_id, references(:users, type: :uuid, on_delete: :delete_all))
|
||||||
|
add(:scheduled_at, :naive_datetime, null: false)
|
||||||
|
add(:params, :map, null: false)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
create(index(:scheduled_activities, [:scheduled_at]))
|
||||||
|
create(index(:scheduled_activities, [:user_id]))
|
||||||
|
end
|
||||||
|
end
|
|
@ -47,16 +47,18 @@ test "it authenticates the auth_user if present and password is correct and rese
|
||||||
|> assign(:auth_user, user)
|
|> assign(:auth_user, user)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
with_mock User,
|
with_mocks([
|
||||||
|
{:crypt, [], [crypt: fn _password, password_hash -> password_hash end]},
|
||||||
|
{User, [],
|
||||||
|
[
|
||||||
reset_password: fn user, %{password: password, password_confirmation: password} ->
|
reset_password: fn user, %{password: password, password_confirmation: password} ->
|
||||||
send(self(), :reset_password)
|
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
end do
|
end
|
||||||
conn
|
]}
|
||||||
|> LegacyAuthenticationPlug.call(%{})
|
]) do
|
||||||
|
LegacyAuthenticationPlug.call(conn, %{})
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_received :reset_password
|
|
||||||
assert conn.assigns.user == user
|
assert conn.assigns.user == user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
64
test/scheduled_activity_test.exs
Normal file
64
test/scheduled_activity_test.exs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.ScheduledActivityTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
alias Pleroma.DataCase
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
setup context do
|
||||||
|
DataCase.ensure_local_uploader(context)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "creation" do
|
||||||
|
test "when daily user limit is exceeded" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
today =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
attrs = %{params: %{}, scheduled_at: today}
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||||
|
{:error, changeset} = ScheduledActivity.create(user, attrs)
|
||||||
|
assert changeset.errors == [scheduled_at: {"daily limit exceeded", []}]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "when total user limit is exceeded" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
today =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
tomorrow =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.hours(36), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: today})
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: today})
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
|
||||||
|
{:error, changeset} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
|
||||||
|
assert changeset.errors == [scheduled_at: {"total limit exceeded", []}]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "when scheduled_at is earlier than 5 minute from now" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
scheduled_at =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.minutes(4), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
attrs = %{params: %{}, scheduled_at: scheduled_at}
|
||||||
|
{:error, changeset} = ScheduledActivity.create(user, attrs)
|
||||||
|
assert changeset.errors == [scheduled_at: {"must be at least 5 minutes from now", []}]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
19
test/scheduled_activity_worker_test.exs
Normal file
19
test/scheduled_activity_worker_test.exs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.ScheduledActivityWorkerTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "creates a status from the scheduled activity" do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_activity = insert(:scheduled_activity, user: user, params: %{status: "hi"})
|
||||||
|
Pleroma.ScheduledActivityWorker.perform(:execute, scheduled_activity.id)
|
||||||
|
|
||||||
|
refute Repo.get(ScheduledActivity, scheduled_activity.id)
|
||||||
|
activity = Repo.all(Pleroma.Activity) |> Enum.find(&(&1.actor == user.ap_id))
|
||||||
|
assert activity.data["object"]["content"] == "hi"
|
||||||
|
end
|
||||||
|
end
|
|
@ -240,6 +240,16 @@ def oauth_token_factory do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def oauth_authorization_factory do
|
||||||
|
%Pleroma.Web.OAuth.Authorization{
|
||||||
|
token: :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false),
|
||||||
|
scopes: ["read", "write", "follow", "push"],
|
||||||
|
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10),
|
||||||
|
user: build(:user),
|
||||||
|
app: build(:oauth_app)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def push_subscription_factory do
|
def push_subscription_factory do
|
||||||
%Pleroma.Web.Push.Subscription{
|
%Pleroma.Web.Push.Subscription{
|
||||||
user: build(:user),
|
user: build(:user),
|
||||||
|
@ -258,6 +268,14 @@ def notification_factory do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def scheduled_activity_factory do
|
||||||
|
%Pleroma.ScheduledActivity{
|
||||||
|
user: build(:user),
|
||||||
|
scheduled_at: NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(60), :millisecond),
|
||||||
|
params: build(:note) |> Map.from_struct() |> Map.get(:data)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def registration_factory do
|
def registration_factory do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,52 @@ test "when the user doesn't exist", %{conn: conn} do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "/api/pleroma/admin/user/follow" do
|
||||||
|
test "allows to force-follow another user" do
|
||||||
|
admin = insert(:user, info: %{is_admin: true})
|
||||||
|
user = insert(:user)
|
||||||
|
follower = insert(:user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, admin)
|
||||||
|
|> put_req_header("accept", "application/json")
|
||||||
|
|> post("/api/pleroma/admin/user/follow", %{
|
||||||
|
"follower" => follower.nickname,
|
||||||
|
"followed" => user.nickname
|
||||||
|
})
|
||||||
|
|
||||||
|
user = User.get_by_id(user.id)
|
||||||
|
follower = User.get_by_id(follower.id)
|
||||||
|
|
||||||
|
assert User.following?(follower, user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "/api/pleroma/admin/user/unfollow" do
|
||||||
|
test "allows to force-unfollow another user" do
|
||||||
|
admin = insert(:user, info: %{is_admin: true})
|
||||||
|
user = insert(:user)
|
||||||
|
follower = insert(:user)
|
||||||
|
|
||||||
|
User.follow(follower, user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, admin)
|
||||||
|
|> put_req_header("accept", "application/json")
|
||||||
|
|> post("/api/pleroma/admin/user/unfollow", %{
|
||||||
|
"follower" => follower.nickname,
|
||||||
|
"followed" => user.nickname
|
||||||
|
})
|
||||||
|
|
||||||
|
user = User.get_by_id(user.id)
|
||||||
|
follower = User.get_by_id(follower.id)
|
||||||
|
|
||||||
|
refute User.following?(follower, user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "PUT /api/pleroma/admin/users/tag" do
|
describe "PUT /api/pleroma/admin/users/tag" do
|
||||||
setup do
|
setup do
|
||||||
admin = insert(:user, info: %{is_admin: true})
|
admin = insert(:user, info: %{is_admin: true})
|
||||||
|
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
@ -2340,4 +2341,281 @@ test "accounts fetches correct account for nicknames beginning with numbers", %{
|
||||||
refute acc_one == acc_two
|
refute acc_one == acc_two
|
||||||
assert acc_two == acc_three
|
assert acc_two == acc_three
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "index/2 redirections" do
|
||||||
|
setup %{conn: conn} do
|
||||||
|
session_opts = [
|
||||||
|
store: :cookie,
|
||||||
|
key: "_test",
|
||||||
|
signing_salt: "cooldude"
|
||||||
|
]
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> Plug.Session.call(Plug.Session.init(session_opts))
|
||||||
|
|> fetch_session()
|
||||||
|
|
||||||
|
test_path = "/web/statuses/test"
|
||||||
|
%{conn: conn, path: test_path}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "redirects not logged-in users to the login page", %{conn: conn, path: path} do
|
||||||
|
conn = get(conn, path)
|
||||||
|
|
||||||
|
assert conn.status == 302
|
||||||
|
assert redirected_to(conn) == "/web/login"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
|
||||||
|
token = insert(:oauth_token)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, token.user)
|
||||||
|
|> put_session(:oauth_token, token.token)
|
||||||
|
|> get(path)
|
||||||
|
|
||||||
|
assert conn.status == 200
|
||||||
|
end
|
||||||
|
|
||||||
|
test "saves referer path to session", %{conn: conn, path: path} do
|
||||||
|
conn = get(conn, path)
|
||||||
|
return_to = Plug.Conn.get_session(conn, :return_to)
|
||||||
|
|
||||||
|
assert return_to == path
|
||||||
|
end
|
||||||
|
|
||||||
|
test "redirects to the saved path after log in", %{conn: conn, path: path} do
|
||||||
|
app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
|
||||||
|
auth = insert(:oauth_authorization, app: app)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_session(:return_to, path)
|
||||||
|
|> get("/web/login", %{code: auth.token})
|
||||||
|
|
||||||
|
assert conn.status == 302
|
||||||
|
assert redirected_to(conn) == path
|
||||||
|
end
|
||||||
|
|
||||||
|
test "redirects to the getting-started page when referer is not present", %{conn: conn} do
|
||||||
|
app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
|
||||||
|
auth = insert(:oauth_authorization, app: app)
|
||||||
|
|
||||||
|
conn = get(conn, "/web/login", %{code: auth.token})
|
||||||
|
|
||||||
|
assert conn.status == 302
|
||||||
|
assert redirected_to(conn) == "/web/getting-started"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "scheduled activities" do
|
||||||
|
test "creates a scheduled activity", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/statuses", %{
|
||||||
|
"status" => "scheduled",
|
||||||
|
"scheduled_at" => scheduled_at
|
||||||
|
})
|
||||||
|
|
||||||
|
assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200)
|
||||||
|
assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(scheduled_at)
|
||||||
|
assert [] == Repo.all(Activity)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "creates a scheduled activity with a media attachment", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
|
||||||
|
|
||||||
|
file = %Plug.Upload{
|
||||||
|
content_type: "image/jpg",
|
||||||
|
path: Path.absname("test/fixtures/image.jpg"),
|
||||||
|
filename: "an_image.jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/statuses", %{
|
||||||
|
"media_ids" => [to_string(upload.id)],
|
||||||
|
"status" => "scheduled",
|
||||||
|
"scheduled_at" => scheduled_at
|
||||||
|
})
|
||||||
|
|
||||||
|
assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200)
|
||||||
|
assert %{"type" => "image"} = media_attachment
|
||||||
|
end
|
||||||
|
|
||||||
|
test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
|
||||||
|
%{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
scheduled_at =
|
||||||
|
NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/statuses", %{
|
||||||
|
"status" => "not scheduled",
|
||||||
|
"scheduled_at" => scheduled_at
|
||||||
|
})
|
||||||
|
|
||||||
|
assert %{"content" => "not scheduled"} = json_response(conn, 200)
|
||||||
|
assert [] == Repo.all(ScheduledActivity)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns error when daily user limit is exceeded", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
today =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
attrs = %{params: %{}, scheduled_at: today}
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
|
||||||
|
|
||||||
|
assert %{"error" => "daily limit exceeded"} == json_response(conn, 422)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns error when total user limit is exceeded", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
today =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
tomorrow =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.hours(36), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
attrs = %{params: %{}, scheduled_at: today}
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
|
||||||
|
|
||||||
|
assert %{"error" => "total limit exceeded"} == json_response(conn, 422)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows scheduled activities", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||||
|
scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||||
|
scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||||
|
scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|
||||||
|
# min_id
|
||||||
|
conn_res =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}")
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
|
||||||
|
|
||||||
|
# since_id
|
||||||
|
conn_res =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}")
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result
|
||||||
|
|
||||||
|
# max_id
|
||||||
|
conn_res =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}")
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows a scheduled activity", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_activity = insert(:scheduled_activity, user: user)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
||||||
|
|
||||||
|
assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200)
|
||||||
|
assert scheduled_activity_id == scheduled_activity.id |> to_string()
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/scheduled_statuses/404")
|
||||||
|
|
||||||
|
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "updates a scheduled activity", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_activity = insert(:scheduled_activity, user: user)
|
||||||
|
|
||||||
|
new_scheduled_at =
|
||||||
|
NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{
|
||||||
|
scheduled_at: new_scheduled_at
|
||||||
|
})
|
||||||
|
|
||||||
|
assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200)
|
||||||
|
assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at})
|
||||||
|
|
||||||
|
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "deletes a scheduled activity", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_activity = insert(:scheduled_activity, user: user)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
||||||
|
|
||||||
|
assert %{} = json_response(res_conn, 200)
|
||||||
|
assert nil == Repo.get(ScheduledActivity, scheduled_activity.id)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
||||||
|
|
||||||
|
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
68
test/web/mastodon_api/scheduled_activity_view_test.exs
Normal file
68
test/web/mastodon_api/scheduled_activity_view_test.exs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.MastodonAPI.ScheduledActivityViewTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
|
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
|
||||||
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "A scheduled activity with a media attachment" do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "hi"})
|
||||||
|
|
||||||
|
scheduled_at =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.minutes(10), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
file = %Plug.Upload{
|
||||||
|
content_type: "image/jpg",
|
||||||
|
path: Path.absname("test/fixtures/image.jpg"),
|
||||||
|
filename: "an_image.jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
|
||||||
|
|
||||||
|
attrs = %{
|
||||||
|
params: %{
|
||||||
|
"media_ids" => [upload.id],
|
||||||
|
"status" => "hi",
|
||||||
|
"sensitive" => true,
|
||||||
|
"spoiler_text" => "spoiler",
|
||||||
|
"visibility" => "unlisted",
|
||||||
|
"in_reply_to_id" => to_string(activity.id)
|
||||||
|
},
|
||||||
|
scheduled_at: scheduled_at
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, scheduled_activity} = ScheduledActivity.create(user, attrs)
|
||||||
|
result = ScheduledActivityView.render("show.json", %{scheduled_activity: scheduled_activity})
|
||||||
|
|
||||||
|
expected = %{
|
||||||
|
id: to_string(scheduled_activity.id),
|
||||||
|
media_attachments:
|
||||||
|
%{"media_ids" => [upload.id]}
|
||||||
|
|> Utils.attachments_from_ids()
|
||||||
|
|> Enum.map(&StatusView.render("attachment.json", %{attachment: &1})),
|
||||||
|
params: %{
|
||||||
|
in_reply_to_id: to_string(activity.id),
|
||||||
|
media_ids: [upload.id],
|
||||||
|
poll: nil,
|
||||||
|
scheduled_at: nil,
|
||||||
|
sensitive: true,
|
||||||
|
spoiler_text: "spoiler",
|
||||||
|
text: "hi",
|
||||||
|
visibility: "unlisted"
|
||||||
|
},
|
||||||
|
scheduled_at: Utils.to_masto_date(scheduled_activity.scheduled_at)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert expected == result
|
||||||
|
end
|
||||||
|
end
|
|
@ -633,6 +633,32 @@ test "rejects token exchange for valid credentials belonging to unconfirmed user
|
||||||
refute Map.has_key?(resp, "access_token")
|
refute Map.has_key?(resp, "access_token")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "rejects token exchange for valid credentials belonging to deactivated user" do
|
||||||
|
password = "testpassword"
|
||||||
|
|
||||||
|
user =
|
||||||
|
insert(:user,
|
||||||
|
password_hash: Comeonin.Pbkdf2.hashpwsalt(password),
|
||||||
|
info: %{deactivated: true}
|
||||||
|
)
|
||||||
|
|
||||||
|
app = insert(:oauth_app)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> post("/oauth/token", %{
|
||||||
|
"grant_type" => "password",
|
||||||
|
"username" => user.nickname,
|
||||||
|
"password" => password,
|
||||||
|
"client_id" => app.client_id,
|
||||||
|
"client_secret" => app.client_secret
|
||||||
|
})
|
||||||
|
|
||||||
|
assert resp = json_response(conn, 403)
|
||||||
|
assert %{"error" => _} = resp
|
||||||
|
refute Map.has_key?(resp, "access_token")
|
||||||
|
end
|
||||||
|
|
||||||
test "rejects an invalid authorization code" do
|
test "rejects an invalid authorization code" do
|
||||||
app = insert(:oauth_app)
|
app = insert(:oauth_app)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue