[#468] Merged `upstream/develop`, resolved conflicts.

This commit is contained in:
Ivan Tashkinov 2019-02-17 14:07:04 +03:00
commit bc4f77b10b
499 changed files with 1402 additions and 423 deletions

View File

@ -19,7 +19,7 @@
# #
# You can give explicit globs or simply directories. # You can give explicit globs or simply directories.
# In the latter case `**/*.{ex,exs}` will be used. # In the latter case `**/*.{ex,exs}` will be used.
included: ["lib/", "src/", "web/", "apps/"], included: ["lib/", "src/", "web/", "apps/", "test/"],
excluded: [~r"/_build/", ~r"/deps/"] excluded: [~r"/_build/", ~r"/deps/"]
}, },
# #
@ -57,7 +57,7 @@
# For some checks, like AliasUsage, you can only customize the priority # For some checks, like AliasUsage, you can only customize the priority
# Priority values are: `low, normal, high, higher` # Priority values are: `low, normal, high, higher`
{Credo.Check.Design.AliasUsage, priority: :low}, {Credo.Check.Design.AliasUsage, priority: :low, if_called_more_often_than: 3},
# For others you can set parameters # For others you can set parameters
@ -104,7 +104,8 @@
{Credo.Check.Warning.BoolOperationOnSameValues}, {Credo.Check.Warning.BoolOperationOnSameValues},
{Credo.Check.Warning.IExPry}, {Credo.Check.Warning.IExPry},
{Credo.Check.Warning.IoInspect}, {Credo.Check.Warning.IoInspect},
{Credo.Check.Warning.LazyLogging}, # Got too much of them, not sure if relevant
{Credo.Check.Warning.LazyLogging, false},
{Credo.Check.Warning.OperationOnSameValues}, {Credo.Check.Warning.OperationOnSameValues},
{Credo.Check.Warning.OperationWithConstantResult}, {Credo.Check.Warning.OperationWithConstantResult},
{Credo.Check.Warning.UnusedEnumOperation}, {Credo.Check.Warning.UnusedEnumOperation},

View File

@ -8,20 +8,7 @@ Pleroma is written in Elixir, high-performance and can run on small devices like
For clients it supports both the [GNU Social API with Qvitter extensions](https://twitter-api.readthedocs.io/en/latest/index.html) and the [Mastodon client API](https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md). For clients it supports both the [GNU Social API with Qvitter extensions](https://twitter-api.readthedocs.io/en/latest/index.html) and the [Mastodon client API](https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md).
Client applications that are committed to supporting Pleroma: - [Client Applications for Pleroma](docs/Clients.md)
* Mastalab (Android, Streaming Ready)
* nekonium (Android, Streaming Ready)
* Tusky (Android, No Streaming)
* Twidere (Android, No Streaming)
* Mast (iOS, Paid)
* Amaroq (iOS, No Streaming)
Client applications that are known to work well:
* Tootdon (Android + iOS, No Streaming)
* Tootle (iOS, No Streaming)
* Whalebird (Windows + Mac + Linux)
No release has been made yet, but several servers have been online for months already. If you want to run your own server, feel free to contact us at @lain@pleroma.soykaf.com or in our dev chat at #pleroma on freenode or via matrix at <https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org>. No release has been made yet, but several servers have been online for months already. If you want to run your own server, feel free to contact us at @lain@pleroma.soykaf.com or in our dev chat at #pleroma on freenode or via matrix at <https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org>.

View File

@ -162,7 +162,9 @@
mrf_transparency: true, mrf_transparency: true,
autofollowed_nicknames: [], autofollowed_nicknames: [],
max_pinned_statuses: 1, max_pinned_statuses: 1,
no_attachment_links: false no_attachment_links: false,
welcome_user_nickname: nil,
welcome_message: nil
config :pleroma, :markup, config :pleroma, :markup,
# XXX - unfortunately, inline images must be enabled by default right now, because # XXX - unfortunately, inline images must be enabled by default right now, because
@ -228,8 +230,8 @@
allow_direct: false allow_direct: false
config :pleroma, :mrf_hellthread, config :pleroma, :mrf_hellthread,
delist_threshold: 5, delist_threshold: 10,
reject_threshold: 10 reject_threshold: 20
config :pleroma, :mrf_simple, config :pleroma, :mrf_simple,
media_removal: [], media_removal: [],

View File

@ -16,7 +16,8 @@
debug_errors: true, debug_errors: true,
code_reloader: true, code_reloader: true,
check_origin: false, check_origin: false,
watchers: [] watchers: [],
secure_cookie_flag: false
config :pleroma, Pleroma.Mailer, adapter: Swoosh.Adapters.Local config :pleroma, Pleroma.Mailer, adapter: Swoosh.Adapters.Local

100
docs/Clients.md Normal file
View File

@ -0,0 +1,100 @@
# Pleroma Clients
Note: Additionnal clients may be working but theses are officially supporting Pleroma.
Feel free to contact us to be added to this list!
## Desktop
### Roma for Desktop
- Homepage: <http://www.pleroma.com/desktop-app/>
- Source Code: ???
- Platforms: Windows, Mac, (Linux?)
- Features: Streaming Ready
### Social
- Source Code: <https://gitlab.gnome.org/BrainBlasted/Social>
- Contact: [@brainblasted@social.libre.fi](https://social.libre.fi/users/brainblasted)
- Platforms: Linux (GNOME)
- Note(2019-01-28): Not at a pre-alpha stage yet
### Whalebird
- Homepage: <https://whalebird.org/>
- Source Code: <https://github.com/h3poteto/whalebird-desktop>
- Contact: [@h3poteto@pleroma.io](https://pleroma.io/users/h3poteto)
- Platforms: Windows, Mac, Linux
- Features: Streaming Ready
## Handheld
### Amaroq
- Homepage: <https://itunes.apple.com/us/app/amaroq-for-mastodon/id1214116200>
- Source Code: <https://github.com/ReticentJohn/Amaroq>
- Contact: [@eurasierboy@mastodon.social](https://mastodon.social/users/eurasierboy)
- Platforms: iOS
- Features: No Streaming
### Nekonium
- Homepage: [F-Droid Repository](https://repo.gdgd.jp.net/), [Google Play](https://play.google.com/store/apps/details?id=com.apps.nekonium), [Amazon](https://www.amazon.co.jp/dp/B076FXPRBC/)
- Source: <https://git.gdgd.jp.net/lin/nekonium/>
- Contact: [@lin@pleroma.gdgd.jp.net](https://pleroma.gdgd.jp.net/users/lin)
- Platforms: Android
- Features: Streaming Ready
### Mastalab
- Source Code: <https://gitlab.com/tom79/mastalab/>
- Contact: [@tom79@mastodon.social](https://mastodon.social/users/tom79)
- Platforms: Android
- Features: Streaming Ready
### Roma
- Homepage: <http://www.pleroma.com/>
- Source Code: ???
- Platforms: iOS, Android
- Features: No Streaming
### Tootdon
- Homepage: <http://tootdon.club/>, <http://blog.mastodon-tootdon.com/>
- Source Code: ???
- Contact: [@tootdon@mstdn.jp](https://mstdn.jp/users/tootdon)
- Platforms: Android, iOS
- Features: No Streaming
### Tusky
- Homepage: <https://tuskyapp.github.io/>
- Source Code: <https://github.com/tuskyapp/Tusky>
- Contact: [@ConnyDuck@mastodon.social](https://mastodon.social/users/ConnyDuck)
- Platforms: Android
- Features: No Streaming
### Twidere
- Homepage: <https://twidere.mariotaku.org/>
- Source Code: <https://github.com/TwidereProject/Twidere-Android/>, <https://github.com/TwidereProject/Twidere-iOS/>
- Contact: <me@mariotaku.org>
- Platform: Android, iOS
- Features: No Streaming
## Alternative Web Interfaces
### Brutaldon
- Homepage: <https://jfm.carcosa.net/projects/software/brutaldon/>
- Source Code: <https://github.com/jfmcbrayer/brutaldon>
- Contact: [@gcupc@glitch.social](https://glitch.social/users/gcupc)
- Features: No Streaming
### Feather
- Source Code: <https://github.com/kaniini/feather>
- Contact: [@kaniini@pleroma.site](https://pleroma.site/kaniini)
- Features: No Streaming
### Halcyon
- Source Code: <https://notabug.org/halcyon-suite/halcyon>
- Contact: [@halcyon@social.csswg.org](https://social.csswg.org/users/halcyon)
- Features: Streaming Ready
### Pinafore
- Homepage: <https://pinafore.social/>
- Source Code: <https://github.com/nolanlawson/pinafore>
- Contact: [@pinafore@mastodon.technology](https://mastodon.technology/users/pinafore)
- Note: Pleroma support is a secondary goal
- Features: No Streaming
### Sengi
- Source Code: <https://github.com/NicolasConstant/sengi>
- Contact: [@sengi_app@mastodon.social](https://mastodon.social/users/sengi_app)
- Note(2019-01-28): The development is currently in a early stage.

View File

@ -97,6 +97,8 @@ config :pleroma, Pleroma.Mailer,
* `max_pinned_statuses`: The maximum number of pinned statuses. `0` will disable the feature. * `max_pinned_statuses`: The maximum number of pinned statuses. `0` will disable the feature.
* `autofollowed_nicknames`: Set to nicknames of (local) users that every new user should automatically follow. * `autofollowed_nicknames`: Set to nicknames of (local) users that every new user should automatically follow.
* `no_attachment_links`: Set to true to disable automatically adding attachment link text to statuses * `no_attachment_links`: Set to true to disable automatically adding attachment link text to statuses
* `welcome_message`: A message that will be send to a newly registered users as a direct message.
* `welcome_user_nickname`: The nickname of the local user that sends the welcome message.
## :logger ## :logger
* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog * `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog

View File

@ -1,7 +1,7 @@
#!/sbin/openrc-run #!/sbin/openrc-run
# Requires OpenRC >= 0.35 # Requires OpenRC >= 0.35
directory=~pleroma/pleroma directory=/opt/pleroma
command=/usr/bin/mix command=/usr/bin/mix
command_args="phx.server" command_args="phx.server"

View File

@ -15,12 +15,13 @@ server {
return 301 https://$server_name$request_uri; return 301 https://$server_name$request_uri;
# Uncomment this if you need to use the 'webroot' method with certbot. Make sure # Uncomment this if you need to use the 'webroot' method with certbot. Make sure
# that you also create the .well-known/acme-challenge directory structure in pleroma/priv/static and # that the directory exists and that it is accessible by the webserver. If you followed
# that is is accessible by the webserver. You may need to load this file with the ssl # the guide, you already ran 'sudo mkdir -p /var/lib/letsencrypt' to create the folder.
# server block commented out, run certbot to get the certificate, and then uncomment it. # You may need to load this file with the ssl server block commented out, run certbot
# to get the certificate, and then uncomment it.
# #
# location ~ /\.well-known/acme-challenge { # location ~ /\.well-known/acme-challenge {
# root <path to install>/pleroma/priv/static/; # root /var/lib/letsencrypt/.well-known/acme-challenge;
# } # }
} }

View File

@ -14,15 +14,17 @@ Environment="MIX_ENV=prod"
; Make sure that all paths fit your installation. ; Make sure that all paths fit your installation.
; Path to the home directory of the user running the Pleroma service. ; Path to the home directory of the user running the Pleroma service.
Environment="HOME=/home/pleroma" Environment="HOME=/var/lib/pleroma"
; Path to the folder containing the Pleroma installation. ; Path to the folder containing the Pleroma installation.
WorkingDirectory=/home/pleroma/pleroma WorkingDirectory=/opt/pleroma
; Path to the Mix binary. ; Path to the Mix binary.
ExecStart=/usr/bin/mix phx.server ExecStart=/usr/bin/mix phx.server
; Some security directives. ; Some security directives.
; Use private /tmp and /var/tmp folders inside a new file system namespace, which are discarded after the process stops. ; Use private /tmp and /var/tmp folders inside a new file system namespace, which are discarded after the process stops.
PrivateTmp=true PrivateTmp=true
; The /home, /root, and /run/user folders can not be accessed by this service anymore. If your Pleroma user has its home folder in one of the restricted places, or use one of these folders as its working directory, you have to set this to false.
ProtectHome=true
; Mount /usr, /boot, and /etc as read-only for processes invoked by this service. ; Mount /usr, /boot, and /etc as read-only for processes invoked by this service.
ProtectSystem=full ProtectSystem=full
; Sets up a new /dev mount for the process and only adds API pseudo devices like /dev/null, /dev/zero or /dev/random but not physical devices. Disabled by default because it may not work on devices like the Raspberry Pi. ; Sets up a new /dev mount for the process and only adds API pseudo devices like /dev/null, /dev/zero or /dev/random but not physical devices. Disabled by default because it may not work on devices like the Raspberry Pi.

View File

@ -4,7 +4,8 @@
defmodule Mix.Tasks.Pleroma.Uploads do defmodule Mix.Tasks.Pleroma.Uploads do
use Mix.Task use Mix.Task
alias Pleroma.{Upload, Uploaders.Local} alias Pleroma.Upload
alias Pleroma.Uploaders.Local
alias Mix.Tasks.Pleroma.Common alias Mix.Tasks.Pleroma.Common
require Logger require Logger
@ -20,7 +21,7 @@ defmodule Mix.Tasks.Pleroma.Uploads do
- `--delete` - delete local uploads after migrating them to the target uploader - `--delete` - delete local uploads after migrating them to the target uploader
A list of avalible uploaders can be seen in config.exs A list of available uploaders can be seen in config.exs
""" """
def run(["migrate_local", target_uploader | args]) do def run(["migrate_local", target_uploader | args]) do
delete? = Enum.member?(args, "--delete") delete? = Enum.member?(args, "--delete")
@ -96,6 +97,7 @@ def run(["migrate_local", target_uploader | args]) do
timeout: 150_000 timeout: 150_000
) )
|> Stream.chunk_every(@log_every) |> Stream.chunk_every(@log_every)
# credo:disable-for-next-line Credo.Check.Warning.UnusedEnumOperation
|> Enum.reduce(0, fn done, count -> |> Enum.reduce(0, fn done, count ->
count = count + length(done) count = count + length(done)
Mix.shell().info("Uploaded #{count}/#{total_count} files") Mix.shell().info("Uploaded #{count}/#{total_count} files")

View File

@ -5,7 +5,8 @@
defmodule Mix.Tasks.Pleroma.User do defmodule Mix.Tasks.Pleroma.User do
use Mix.Task use Mix.Task
import Ecto.Changeset import Ecto.Changeset
alias Pleroma.{Repo, User} alias Pleroma.Repo
alias Pleroma.User
alias Mix.Tasks.Pleroma.Common alias Mix.Tasks.Pleroma.Common
@shortdoc "Manages Pleroma users" @shortdoc "Manages Pleroma users"
@ -211,7 +212,7 @@ def run(["unsubscribe", nickname]) do
user = Repo.get(User, user.id) user = Repo.get(User, user.id)
if length(user.following) == 0 do if Enum.empty?(user.following) do
Mix.shell().info("Successfully unsubscribed all followers from #{user.nickname}") Mix.shell().info("Successfully unsubscribed all followers from #{user.nickname}")
end end
else else

View File

@ -7,7 +7,9 @@ defmodule Pleroma.PasswordResetToken do
import Ecto.Changeset import Ecto.Changeset
alias Pleroma.{User, PasswordResetToken, Repo} alias Pleroma.User
alias Pleroma.Repo
alias Pleroma.PasswordResetToken
schema "password_reset_tokens" do schema "password_reset_tokens" do
belongs_to(:user, User, type: Pleroma.FlakeId) belongs_to(:user, User, type: Pleroma.FlakeId)

View File

@ -4,7 +4,11 @@
defmodule Pleroma.Activity do defmodule Pleroma.Activity do
use Ecto.Schema use Ecto.Schema
alias Pleroma.{Repo, Activity, Notification}
alias Pleroma.Repo
alias Pleroma.Activity
alias Pleroma.Notification
import Ecto.Query import Ecto.Query
@type t :: %__MODULE__{} @type t :: %__MODULE__{}

View File

@ -3,9 +3,9 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Captcha do defmodule Pleroma.Captcha do
alias Calendar.DateTime
alias Plug.Crypto.KeyGenerator alias Plug.Crypto.KeyGenerator
alias Plug.Crypto.MessageEncryptor alias Plug.Crypto.MessageEncryptor
alias Calendar.DateTime
use GenServer use GenServer

View File

@ -7,7 +7,8 @@ defmodule Pleroma.UserEmail do
import Swoosh.Email import Swoosh.Email
alias Pleroma.Web.{Endpoint, Router} alias Pleroma.Web.Endpoint
alias Pleroma.Web.Router
defp instance_config, do: Pleroma.Config.get(:instance) defp instance_config, do: Pleroma.Config.get(:instance)

View File

@ -4,8 +4,12 @@
defmodule Pleroma.Filter do defmodule Pleroma.Filter do
use Ecto.Schema use Ecto.Schema
import Ecto.{Changeset, Query}
alias Pleroma.{User, Repo} import Ecto.Changeset
import Ecto.Query
alias Pleroma.User
alias Pleroma.Repo
schema "filters" do schema "filters" do
belongs_to(:user, User, type: Pleroma.FlakeId) belongs_to(:user, User, type: Pleroma.FlakeId)

View File

@ -27,7 +27,7 @@ def to_string(<<0::integer-size(64), id::integer-size(64)>>) do
Kernel.to_string(id) Kernel.to_string(id)
end end
def to_string(flake = <<_::integer-size(64), _::integer-size(48), _::integer-size(16)>>) do def to_string(<<_::integer-size(64), _::integer-size(48), _::integer-size(16)>> = flake) do
encode_base62(flake) encode_base62(flake)
end end
@ -42,7 +42,7 @@ def from_string(unquote(i)), do: <<0::integer-size(128)>>
def from_string(unquote(Kernel.to_string(i))), do: <<0::integer-size(128)>> def from_string(unquote(Kernel.to_string(i))), do: <<0::integer-size(128)>>
end end
def from_string(flake = <<_::integer-size(128)>>), do: flake def from_string(<<_::integer-size(128)>> = flake), do: flake
def from_string(string) when is_binary(string) and byte_size(string) < 18 do def from_string(string) when is_binary(string) and byte_size(string) < 18 do
case Integer.parse(string) do case Integer.parse(string) do

View File

@ -3,10 +3,10 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Formatter do defmodule Pleroma.Formatter do
alias Pleroma.Emoji
alias Pleroma.HTML
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy
alias Pleroma.HTML
alias Pleroma.Emoji
@tag_regex ~r/((?<=[^&])|\A)(\#)(\w+)/u @tag_regex ~r/((?<=[^&])|\A)(\#)(\w+)/u
@markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/ @markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/

View File

@ -37,17 +37,17 @@ def init([ip, port]) do
defmodule Pleroma.Gopher.Server.ProtocolHandler do defmodule Pleroma.Gopher.Server.ProtocolHandler do
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.User
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Repo
alias Pleroma.HTML alias Pleroma.HTML
alias Pleroma.User
alias Pleroma.Repo
def start_link(ref, socket, transport, opts) do def start_link(ref, socket, transport, opts) do
pid = spawn_link(__MODULE__, :init, [ref, socket, transport, opts]) pid = spawn_link(__MODULE__, :init, [ref, socket, transport, opts])
{:ok, pid} {:ok, pid}
end end
def init(ref, socket, transport, _Opts = []) do def init(ref, socket, transport, [] = _Opts) do
:ok = :ranch.accept_ack(ref) :ok = :ranch.accept_ack(ref)
loop(socket, transport) loop(socket, transport)
end end

View File

@ -83,8 +83,7 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
""" """
@markup Application.get_env(:pleroma, :markup) @markup Application.get_env(:pleroma, :markup)
@uri_schemes Application.get_env(:pleroma, :uri_schemes, []) @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
@valid_schemes Keyword.get(@uri_schemes, :valid_schemes, [])
require HtmlSanitizeEx.Scrubber.Meta require HtmlSanitizeEx.Scrubber.Meta
alias HtmlSanitizeEx.Scrubber.Meta alias HtmlSanitizeEx.Scrubber.Meta
@ -126,10 +125,11 @@ defmodule Pleroma.HTML.Scrubber.Default do
require HtmlSanitizeEx.Scrubber.Meta require HtmlSanitizeEx.Scrubber.Meta
alias HtmlSanitizeEx.Scrubber.Meta alias HtmlSanitizeEx.Scrubber.Meta
# credo:disable-for-previous-line
# No idea how to fix this one…
@markup Application.get_env(:pleroma, :markup) @markup Application.get_env(:pleroma, :markup)
@uri_schemes Application.get_env(:pleroma, :uri_schemes, []) @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
@valid_schemes Keyword.get(@uri_schemes, :valid_schemes, [])
Meta.remove_cdata_sections_before_scrub() Meta.remove_cdata_sections_before_scrub()
Meta.strip_comments() Meta.strip_comments()

View File

@ -2,13 +2,13 @@ defmodule Pleroma.Instances.Instance do
@moduledoc "Instance." @moduledoc "Instance."
alias Pleroma.Instances alias Pleroma.Instances
alias Pleroma.Repo
alias Pleroma.Instances.Instance alias Pleroma.Instances.Instance
use Ecto.Schema use Ecto.Schema
import Ecto.{Query, Changeset} import Ecto.Query
import Ecto.Changeset
alias Pleroma.Repo
schema "instances" do schema "instances" do
field(:host, :string) field(:host, :string)

View File

@ -4,8 +4,13 @@
defmodule Pleroma.List do defmodule Pleroma.List do
use Ecto.Schema use Ecto.Schema
import Ecto.{Changeset, Query}
alias Pleroma.{User, Repo, Activity} import Ecto.Query
import Ecto.Changeset
alias Pleroma.Activity
alias Pleroma.Repo
alias Pleroma.User
schema "lists" do schema "lists" do
belongs_to(:user, User, type: Pleroma.FlakeId) belongs_to(:user, User, type: Pleroma.FlakeId)

View File

@ -4,8 +4,14 @@
defmodule Pleroma.Notification do defmodule Pleroma.Notification do
use Ecto.Schema use Ecto.Schema
alias Pleroma.{User, Activity, Notification, Repo}
alias Pleroma.User
alias Pleroma.Activity
alias Pleroma.Notification
alias Pleroma.Repo
alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.CommonAPI
import Ecto.Query import Ecto.Query
schema "notifications" do schema "notifications" do
@ -112,7 +118,7 @@ def create_notifications(_), do: {:ok, []}
# TODO move to sql, too. # TODO move to sql, too.
def create_notification(%Activity{} = activity, %User{} = user) do def create_notification(%Activity{} = activity, %User{} = user) do
unless User.blocks?(user, %{ap_id: activity.data["actor"]}) or unless User.blocks?(user, %{ap_id: activity.data["actor"]}) or
user.ap_id == activity.data["actor"] or CommonAPI.thread_muted?(user, activity) or user.ap_id == activity.data["actor"] or
(activity.data["type"] == "Follow" and (activity.data["type"] == "Follow" and
Enum.any?(Notification.for_user(user), fn notif -> Enum.any?(Notification.for_user(user), fn notif ->
notif.activity.data["type"] == "Follow" and notif.activity.data["type"] == "Follow" and

View File

@ -4,8 +4,15 @@
defmodule Pleroma.Object do defmodule Pleroma.Object do
use Ecto.Schema use Ecto.Schema
alias Pleroma.{Repo, Object, User, Activity, ObjectTombstone}
import Ecto.{Query, Changeset} alias Pleroma.Repo
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Activity
alias Pleroma.ObjectTombstone
import Ecto.Query
import Ecto.Changeset
schema "objects" do schema "objects" do
field(:data, :map) field(:data, :map)

View File

@ -33,7 +33,22 @@ defp headers do
end end
defp csp_string do defp csp_string do
protocol = Config.get([Pleroma.Web.Endpoint, :protocol]) scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
websocket_url = String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws")
connect_src =
if Mix.env() == :dev do
"connect-src 'self' http://localhost:3035/ " <> websocket_url
else
"connect-src 'self' " <> websocket_url
end
script_src =
if Mix.env() == :dev do
"script-src 'self' 'unsafe-eval'"
else
"script-src 'self'"
end
[ [
"default-src 'none'", "default-src 'none'",
@ -43,10 +58,10 @@ defp csp_string do
"media-src 'self' https:", "media-src 'self' https:",
"style-src 'self' 'unsafe-inline'", "style-src 'self' 'unsafe-inline'",
"font-src 'self'", "font-src 'self'",
"script-src 'self'",
"connect-src 'self' " <> String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws"),
"manifest-src 'self'", "manifest-src 'self'",
if protocol == "https" do connect_src,
script_src,
if scheme == "https" do
"upgrade-insecure-requests" "upgrade-insecure-requests"
end end
] ]

View File

@ -33,7 +33,7 @@ def init(opts) do
for only <- @only do for only <- @only do
at = Plug.Router.Utils.split("/") at = Plug.Router.Utils.split("/")
def call(conn = %{request_path: "/" <> unquote(only) <> _}, opts) do def call(%{request_path: "/" <> unquote(only) <> _} = conn, opts) do
call_static( call_static(
conn, conn,
opts, opts,

View File

@ -6,11 +6,9 @@ defmodule Pleroma.Plugs.OAuthPlug do
import Plug.Conn import Plug.Conn
import Ecto.Query import Ecto.Query
alias Pleroma.{ alias Pleroma.User
User, alias Pleroma.Repo
Repo, alias Pleroma.Web.OAuth.Token
Web.OAuth.Token
}
@realm_reg Regex.compile!("Bearer\:?\s+(.*)$", "i") @realm_reg Regex.compile!("Bearer\:?\s+(.*)$", "i")

View File

@ -23,7 +23,7 @@ def init(_opts) do
%{static_plug_opts: static_plug_opts} %{static_plug_opts: static_plug_opts}
end end
def call(conn = %{request_path: <<"/", @path, "/", file::binary>>}, opts) do def call(%{request_path: <<"/", @path, "/", file::binary>>} = conn, opts) do
config = Pleroma.Config.get([Pleroma.Upload]) config = Pleroma.Config.get([Pleroma.Upload])
with uploader <- Keyword.fetch!(config, :uploader), with uploader <- Keyword.fetch!(config, :uploader),

View File

@ -3,9 +3,10 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Plugs.UserFetcherPlug do defmodule Pleroma.Plugs.UserFetcherPlug do
import Plug.Conn
alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Repo
import Plug.Conn
def init(options) do def init(options) do
options options

View File

@ -4,7 +4,8 @@
defmodule Pleroma.Stats do defmodule Pleroma.Stats do
import Ecto.Query import Ecto.Query
alias Pleroma.{User, Repo} alias Pleroma.User
alias Pleroma.Repo
def start_link do def start_link do
agent = Agent.start_link(fn -> {[], %{}} end, name: __MODULE__) agent = Agent.start_link(fn -> {[], %{}} end, name: __MODULE__)
@ -23,7 +24,7 @@ def get_peers do
def schedule_update do def schedule_update do
spawn(fn -> spawn(fn ->
# 1 hour # 1 hour
Process.sleep(1000 * 60 * 60 * 1) Process.sleep(1000 * 60 * 60)
schedule_update() schedule_update()
end) end)

View File

@ -0,0 +1,45 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ThreadMute do
use Ecto.Schema
alias Pleroma.{Repo, User, ThreadMute}
require Ecto.Query
schema "thread_mutes" do
belongs_to(:user, User, type: Pleroma.FlakeId)
field(:context, :string)
end
def changeset(mute, params \\ %{}) do
mute
|> Ecto.Changeset.cast(params, [:user_id, :context])
|> Ecto.Changeset.foreign_key_constraint(:user_id)
|> Ecto.Changeset.unique_constraint(:user_id, name: :unique_index)
end
def query(user_id, context) do
user_id = Pleroma.FlakeId.from_string(user_id)
ThreadMute
|> Ecto.Query.where(user_id: ^user_id)
|> Ecto.Query.where(context: ^context)
end
def add_mute(user_id, context) do
%ThreadMute{}
|> changeset(%{user_id: user_id, context: context})
|> Repo.insert()
end
def remove_mute(user_id, context) do
query(user_id, context)
|> Repo.delete_all()
end
def check_muted(user_id, context) do
query(user_id, context)
|> Repo.all()
end
end

View File

@ -180,7 +180,7 @@ defp prepare_upload(%{"img" => "data:image/" <> image_data}, opts) do
end end
# For Mix.Tasks.MigrateLocalUploads # For Mix.Tasks.MigrateLocalUploads
defp prepare_upload(upload = %__MODULE__{tempfile: path}, _opts) do defp prepare_upload(%__MODULE__{tempfile: path} = upload, _opts) do
with {:ok, content_type} <- Pleroma.MIME.file_mime_type(path) do with {:ok, content_type} <- Pleroma.MIME.file_mime_type(path) do
{:ok, %__MODULE__{upload | content_type: content_type}} {:ok, %__MODULE__{upload | content_type: content_type}}
end end

View File

@ -6,7 +6,7 @@ defmodule Pleroma.Upload.Filter.Dedupe do
@behaviour Pleroma.Upload.Filter @behaviour Pleroma.Upload.Filter
alias Pleroma.Upload alias Pleroma.Upload
def filter(upload = %Upload{name: name}) do def filter(%Upload{name: name} = upload) do
extension = String.split(name, ".") |> List.last() extension = String.split(name, ".") |> List.last()
shasum = :crypto.hash(:sha256, File.read!(upload.tempfile)) |> Base.encode16(case: :lower) shasum = :crypto.hash(:sha256, File.read!(upload.tempfile)) |> Base.encode16(case: :lower)
filename = shasum <> "." <> extension filename = shasum <> "." <> extension

View File

@ -25,7 +25,7 @@ def put_file(upload) do
query = "#{cgi}?#{extension}" query = "#{cgi}?#{extension}"
with {:ok, %{status: 200, body: body}} <- with {:ok, %{status: 200, body: body}} <-
@httpoison.post(query, file_data, adapter: [pool: :default]) do @httpoison.post(query, file_data, [], adapter: [pool: :default]) do
remote_file_name = String.split(body) |> List.first() remote_file_name = String.split(body) |> List.first()
public_url = "#{files}/#{remote_file_name}.#{extension}" public_url = "#{files}/#{remote_file_name}.#{extension}"
{:ok, {:url, public_url}} {:ok, {:url, public_url}}

View File

@ -27,7 +27,7 @@ def get_file(file) do
])}} ])}}
end end
def put_file(upload = %Pleroma.Upload{}) do def put_file(%Pleroma.Upload{} = upload) do
config = Pleroma.Config.get([__MODULE__]) config = Pleroma.Config.get([__MODULE__])
bucket = Keyword.get(config, :bucket) bucket = Keyword.get(config, :bucket)

View File

@ -5,13 +5,23 @@
defmodule Pleroma.User do defmodule Pleroma.User do
use Ecto.Schema use Ecto.Schema
import Ecto.{Changeset, Query} import Ecto.Changeset
alias Pleroma.{Repo, User, Object, Web, Activity, Notification} import Ecto.Query
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Object
alias Pleroma.Web
alias Pleroma.Activity
alias Pleroma.Notification
alias Comeonin.Pbkdf2 alias Comeonin.Pbkdf2
alias Pleroma.Formatter alias Pleroma.Formatter
alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
alias Pleroma.Web.{OStatus, Websub, OAuth} alias Pleroma.Web.OStatus
alias Pleroma.Web.ActivityPub.{Utils, ActivityPub} alias Pleroma.Web.Websub
alias Pleroma.Web.OAuth
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.ActivityPub
require Logger require Logger
@ -96,12 +106,6 @@ def ap_followers(%User{} = user) do
"#{ap_id(user)}/followers" "#{ap_id(user)}/followers"
end end
def follow_changeset(struct, params \\ %{}) do
struct
|> cast(params, [:following])
|> validate_required([:following])
end
def user_info(%User{} = user) do def user_info(%User{} = user) do
oneself = if user.local, do: 1, else: 0 oneself = if user.local, do: 1, else: 0
@ -256,8 +260,9 @@ defp autofollow_users(user) do
@doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)" @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
def register(%Ecto.Changeset{} = changeset) do def register(%Ecto.Changeset{} = changeset) do
with {:ok, user} <- Repo.insert(changeset), with {:ok, user} <- Repo.insert(changeset),
{:ok, _} <- try_send_confirmation_email(user), {:ok, user} <- autofollow_users(user),
{:ok, user} <- autofollow_users(user) do {:ok, _} <- Pleroma.User.WelcomeMessage.post_welcome_message_to_user(user),
{:ok, _} <- try_send_confirmation_email(user) do
{:ok, user} {:ok, user}
end end
end end
@ -307,10 +312,13 @@ def maybe_follow(%User{} = follower, %User{info: _info} = followed) do
end end
end end
@doc "A mass follow for local users. Ignores blocks and has no side effects" @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
@spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()} @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
def follow_all(follower, followeds) do def follow_all(follower, followeds) do
followed_addresses = Enum.map(followeds, fn %{follower_address: fa} -> fa end) followed_addresses =
followeds
|> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
|> Enum.map(fn %{follower_address: fa} -> fa end)
q = q =
from(u in User, from(u in User,
@ -724,7 +732,7 @@ def search(query, resolve \\ false, for_user \\ nil) do
# Strip the beginning @ off if there is a query # Strip the beginning @ off if there is a query
query = String.trim_leading(query, "@") query = String.trim_leading(query, "@")
if resolve, do: User.get_or_fetch_by_nickname(query) if resolve, do: get_or_fetch(query)
fts_results = do_search(fts_search_subquery(query), for_user) fts_results = do_search(fts_search_subquery(query), for_user)

View File

@ -0,0 +1,30 @@
defmodule Pleroma.User.WelcomeMessage do
alias Pleroma.User
alias Pleroma.Web.CommonAPI
def post_welcome_message_to_user(user) do
with %User{} = sender_user <- welcome_user(),
message when is_binary(message) <- welcome_message() do
CommonAPI.post(sender_user, %{
"visibility" => "direct",
"status" => "@#{user.nickname}\n#{message}"
})
else
_ -> {:ok, nil}
end
end
defp welcome_user() do
with nickname when is_binary(nickname) <-
Pleroma.Config.get([:instance, :welcome_user_nickname]),
%User{local: true} = user <- User.get_cached_by_nickname(nickname) do
user
else
_ -> nil
end
end
defp welcome_message() do
Pleroma.Config.get([:instance, :welcome_message])
end
end

View File

@ -3,13 +3,22 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ActivityPub do defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.{Activity, Repo, Object, Upload, User, Notification, Instances} alias Pleroma.Activity
alias Pleroma.Web.ActivityPub.{Transmogrifier, MRF} alias Pleroma.Repo
alias Pleroma.Object
alias Pleroma.Upload
alias Pleroma.User
alias Pleroma.Notification
alias Pleroma.Instances
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.MRF
alias Pleroma.Web.WebFinger alias Pleroma.Web.WebFinger
alias Pleroma.Web.Federator alias Pleroma.Web.Federator
alias Pleroma.Web.OStatus alias Pleroma.Web.OStatus
import Ecto.Query import Ecto.Query
import Pleroma.Web.ActivityPub.Utils import Pleroma.Web.ActivityPub.Utils
require Logger require Logger
@httpoison Application.get_env(:pleroma, :httpoison) @httpoison Application.get_env(:pleroma, :httpoison)
@ -19,10 +28,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp get_recipients(%{"type" => "Announce"} = data) do defp get_recipients(%{"type" => "Announce"} = data) do
to = data["to"] || [] to = data["to"] || []
cc = data["cc"] || [] cc = data["cc"] || []
recipients = to ++ cc
actor = User.get_cached_by_ap_id(data["actor"]) actor = User.get_cached_by_ap_id(data["actor"])
recipients recipients =
(to ++ cc)
|> Enum.filter(fn recipient -> |> Enum.filter(fn recipient ->
case User.get_cached_by_ap_id(recipient) do case User.get_cached_by_ap_id(recipient) do
nil -> nil ->
@ -119,7 +128,7 @@ def stream_out(activity) do
activity.data["object"] activity.data["object"]
|> Map.get("tag", []) |> Map.get("tag", [])
|> Enum.filter(fn tag -> is_bitstring(tag) end) |> Enum.filter(fn tag -> is_bitstring(tag) end)
|> Enum.map(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end) |> Enum.each(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end)
if activity.data["object"]["attachment"] != [] do if activity.data["object"]["attachment"] != [] do
Pleroma.Web.Streamer.stream("public:media", activity) Pleroma.Web.Streamer.stream("public:media", activity)
@ -809,8 +818,6 @@ def fetch_object_from_id(id) do
if object = Object.get_cached_by_ap_id(id) do if object = Object.get_cached_by_ap_id(id) do
{:ok, object} {:ok, object}
else else
Logger.info("Fetching #{id} via AP")
with {:ok, data} <- fetch_and_contain_remote_object_from_id(id), with {:ok, data} <- fetch_and_contain_remote_object_from_id(id),
nil <- Object.normalize(data), nil <- Object.normalize(data),
params <- %{ params <- %{
@ -842,7 +849,7 @@ def fetch_object_from_id(id) do
end end
def fetch_and_contain_remote_object_from_id(id) do def fetch_and_contain_remote_object_from_id(id) do
Logger.info("Fetching #{id} via AP") Logger.info("Fetching object #{id} via AP")
with true <- String.starts_with?(id, "http"), with true <- String.starts_with?(id, "http"),
{:ok, %{body: body, status: code}} when code in 200..299 <- {:ok, %{body: body, status: code}} when code in 200..299 <-

View File

@ -5,12 +5,15 @@
defmodule Pleroma.Web.ActivityPub.ActivityPubController do defmodule Pleroma.Web.ActivityPub.ActivityPubController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.{Activity, User, Object} alias Pleroma.Activity
alias Pleroma.Web.ActivityPub.{ObjectView, UserView} alias Pleroma.User
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.UserView
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Federator alias Pleroma.Web.Federator
require Logger require Logger

View File

@ -6,40 +6,80 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
alias Pleroma.User alias Pleroma.User
@behaviour Pleroma.Web.ActivityPub.MRF @behaviour Pleroma.Web.ActivityPub.MRF
defp delist_message(message) do defp delist_message(message, threshold) when threshold > 0 do
follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address
follower_collection? = Enum.member?(message["to"] ++ message["cc"], follower_collection)
message =
case recipients = get_recipient_count(message) do
{:public, _}
when follower_collection? and recipients > threshold ->
message message
|> Map.put("to", [follower_collection]) |> Map.put("to", [follower_collection])
|> Map.put("cc", ["https://www.w3.org/ns/activitystreams#Public"]) |> Map.put("cc", ["https://www.w3.org/ns/activitystreams#Public"])
{:public, _} when recipients > threshold ->
message
|> Map.put("to", [])
|> Map.put("cc", ["https://www.w3.org/ns/activitystreams#Public"])
_ ->
message
end
{:ok, message}
end
defp delist_message(message, _threshold), do: {:ok, message}
defp reject_message(message, threshold) when threshold > 0 do
with {_, recipients} <- get_recipient_count(message) do
if recipients > threshold do
{:reject, nil}
else
{:ok, message}
end
end
end
defp reject_message(message, _threshold), do: {:ok, message}
defp get_recipient_count(message) do
recipients = (message["to"] || []) ++ (message["cc"] || [])
follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address
if Enum.member?(recipients, "https://www.w3.org/ns/activitystreams#Public") do
recipients =
recipients
|> List.delete("https://www.w3.org/ns/activitystreams#Public")
|> List.delete(follower_collection)
{:public, length(recipients)}
else
recipients =
recipients
|> List.delete(follower_collection)
{:not_public, length(recipients)}
end
end end
@impl true @impl true
def filter(%{"type" => "Create"} = message) do def filter(%{"type" => "Create"} = message) do
delist_threshold = Pleroma.Config.get([:mrf_hellthread, :delist_threshold])
reject_threshold = reject_threshold =
Pleroma.Config.get( Pleroma.Config.get(
[:mrf_hellthread, :reject_threshold], [:mrf_hellthread, :reject_threshold],
Pleroma.Config.get([:mrf_hellthread, :threshold]) Pleroma.Config.get([:mrf_hellthread, :threshold])
) )
recipients = (message["to"] || []) ++ (message["cc"] || []) delist_threshold = Pleroma.Config.get([:mrf_hellthread, :delist_threshold])
cond do with {:ok, message} <- reject_message(message, reject_threshold),
length(recipients) > reject_threshold and reject_threshold > 0 -> {:ok, message} <- delist_message(message, delist_threshold) do
{:reject, nil} {:ok, message}
length(recipients) > delist_threshold and delist_threshold > 0 ->
if Enum.member?(message["to"], "https://www.w3.org/ns/activitystreams#Public") or
Enum.member?(message["cc"], "https://www.w3.org/ns/activitystreams#Public") do
{:ok, delist_message(message)}
else else
{:ok, message} _e -> {:reject, nil}
end
true ->
{:ok, message}
end end
end end

View File

@ -12,9 +12,9 @@ defp string_matches?(string, pattern) do
String.match?(string, pattern) String.match?(string, pattern)
end end
defp check_reject(%{"object" => %{"content" => content}} = message) do defp check_reject(%{"object" => %{"content" => content, "summary" => summary}} = message) do
if Enum.any?(Pleroma.Config.get([:mrf_keyword, :reject]), fn pattern -> if Enum.any?(Pleroma.Config.get([:mrf_keyword, :reject]), fn pattern ->
string_matches?(content, pattern) string_matches?(content, pattern) or string_matches?(summary, pattern)
end) do end) do
{:reject, nil} {:reject, nil}
else else
@ -22,10 +22,12 @@ defp check_reject(%{"object" => %{"content" => content}} = message) do
end end
end end
defp check_ftl_removal(%{"to" => to, "object" => %{"content" => content}} = message) do defp check_ftl_removal(
%{"to" => to, "object" => %{"content" => content, "summary" => summary}} = message
) do
if "https://www.w3.org/ns/activitystreams#Public" in to and if "https://www.w3.org/ns/activitystreams#Public" in to and
Enum.any?(Pleroma.Config.get([:mrf_keyword, :federated_timeline_removal]), fn pattern -> Enum.any?(Pleroma.Config.get([:mrf_keyword, :federated_timeline_removal]), fn pattern ->
string_matches?(content, pattern) string_matches?(content, pattern) or string_matches?(summary, pattern)
end) do end) do
to = List.delete(to, "https://www.w3.org/ns/activitystreams#Public") to = List.delete(to, "https://www.w3.org/ns/activitystreams#Public")
cc = ["https://www.w3.org/ns/activitystreams#Public" | message["cc"] || []] cc = ["https://www.w3.org/ns/activitystreams#Public" | message["cc"] || []]
@ -41,14 +43,20 @@ defp check_ftl_removal(%{"to" => to, "object" => %{"content" => content}} = mess
end end
end end
defp check_replace(%{"object" => %{"content" => content}} = message) do defp check_replace(%{"object" => %{"content" => content, "summary" => summary}} = message) do
content = {content, summary} =
Enum.reduce(Pleroma.Config.get([:mrf_keyword, :replace]), content, fn {pattern, replacement}, Enum.reduce(Pleroma.Config.get([:mrf_keyword, :replace]), {content, summary}, fn {pattern,
acc -> replacement},
String.replace(acc, pattern, replacement) {content_acc,
summary_acc} ->
{String.replace(content_acc, pattern, replacement),
String.replace(summary_acc, pattern, replacement)}
end) end)
{:ok, put_in(message["object"]["content"], content)} {:ok,
message
|> put_in(["object", "content"], content)
|> put_in(["object", "summary"], summary)}
end end
@impl true @impl true

View File

@ -3,7 +3,9 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Relay do defmodule Pleroma.Web.ActivityPub.Relay do
alias Pleroma.{User, Object, Activity} alias Pleroma.User
alias Pleroma.Object
alias Pleroma.Activity
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
require Logger require Logger

View File

@ -6,9 +6,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
@moduledoc """ @moduledoc """
A module to handle coding from internal to wire ActivityPub and back. A module to handle coding from internal to wire ActivityPub and back.
""" """
alias Pleroma.Activity
alias Pleroma.User alias Pleroma.User
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Activity
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
@ -649,7 +649,7 @@ def get_obj_helper(id) do
if object = Object.normalize(id), do: {:ok, object}, else: nil if object = Object.normalize(id), do: {:ok, object}, else: nil
end end
def set_reply_to_uri(%{"inReplyTo" => inReplyTo} = object) do def set_reply_to_uri(%{"inReplyTo" => inReplyTo} = object) when is_binary(inReplyTo) do
with false <- String.starts_with?(inReplyTo, "http"), with false <- String.starts_with?(inReplyTo, "http"),
{:ok, %{data: replied_to_object}} <- get_obj_helper(inReplyTo) do {:ok, %{data: replied_to_object}} <- get_obj_helper(inReplyTo) do
Map.put(object, "inReplyTo", replied_to_object["external_url"] || inReplyTo) Map.put(object, "inReplyTo", replied_to_object["external_url"] || inReplyTo)
@ -765,12 +765,18 @@ def maybe_fix_object_url(data) do
def add_hashtags(object) do def add_hashtags(object) do
tags = tags =
(object["tag"] || []) (object["tag"] || [])
|> Enum.map(fn tag -> |> Enum.map(fn
# Expand internal representation tags into AS2 tags.
tag when is_binary(tag) ->
%{ %{
"href" => Pleroma.Web.Endpoint.url() <> "/tags/#{tag}", "href" => Pleroma.Web.Endpoint.url() <> "/tags/#{tag}",
"name" => "##{tag}", "name" => "##{tag}",
"type" => "Hashtag" "type" => "Hashtag"
} }
# Do not process tags which are already AS2 tag objects.
tag when is_map(tag) ->
tag
end) end)
object object

View File

@ -3,11 +3,19 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Utils do defmodule Pleroma.Web.ActivityPub.Utils do
alias Pleroma.{Repo, Web, Object, Activity, User, Notification} alias Pleroma.Repo
alias Pleroma.Web
alias Pleroma.Object
alias Pleroma.Activity
alias Pleroma.User
alias Pleroma.Notification
alias Pleroma.Web.Router.Helpers alias Pleroma.Web.Router.Helpers
alias Pleroma.Web.Endpoint alias Pleroma.Web.Endpoint
alias Ecto.{Changeset, UUID} alias Ecto.Changeset
alias Ecto.UUID
import Ecto.Query import Ecto.Query
require Logger require Logger
@supported_object_types ["Article", "Note", "Video", "Page"] @supported_object_types ["Article", "Note", "Video", "Page"]

View File

@ -4,7 +4,8 @@
defmodule Pleroma.Web.ActivityPub.ObjectView do defmodule Pleroma.Web.ActivityPub.ObjectView do
use Pleroma.Web, :view use Pleroma.Web, :view
alias Pleroma.{Object, Activity} alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
def render("object.json", %{object: %Object{} = object}) do def render("object.json", %{object: %Object{} = object}) do

View File

@ -4,15 +4,34 @@
defmodule Pleroma.Web.ActivityPub.UserView do defmodule Pleroma.Web.ActivityPub.UserView do
use Pleroma.Web, :view use Pleroma.Web, :view
alias Pleroma.Web.Salmon
alias Pleroma.Web.WebFinger alias Pleroma.Web.WebFinger
alias Pleroma.Web.Salmon
alias Pleroma.User alias Pleroma.User
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Router.Helpers
alias Pleroma.Web.Endpoint
import Ecto.Query import Ecto.Query
def render("endpoints.json", %{user: %User{nickname: nil, local: true} = _user}) do
%{"sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox)}
end
def render("endpoints.json", %{user: %User{local: true} = _user}) do
%{
"oauthAuthorizationEndpoint" => Helpers.o_auth_url(Endpoint, :authorize),
"oauthRegistrationEndpoint" => Helpers.mastodon_api_url(Endpoint, :create_app),
"oauthTokenEndpoint" => Helpers.o_auth_url(Endpoint, :token_exchange),
"sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox)
}
end
def render("endpoints.json", _), do: %{}
# the instance itself is not a Person, but instead an Application # the instance itself is not a Person, but instead an Application
def render("user.json", %{user: %{nickname: nil} = user}) do def render("user.json", %{user: %{nickname: nil} = user}) do
{:ok, user} = WebFinger.ensure_keys_present(user) {:ok, user} = WebFinger.ensure_keys_present(user)
@ -20,6 +39,8 @@ def render("user.json", %{user: %{nickname: nil} = user}) do
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
public_key = :public_key.pem_encode([public_key]) public_key = :public_key.pem_encode([public_key])
endpoints = render("endpoints.json", %{user: user})
%{ %{
"id" => user.ap_id, "id" => user.ap_id,
"type" => "Application", "type" => "Application",
@ -35,9 +56,7 @@ def render("user.json", %{user: %{nickname: nil} = user}) do
"owner" => user.ap_id, "owner" => user.ap_id,
"publicKeyPem" => public_key "publicKeyPem" => public_key
}, },
"endpoints" => %{ "endpoints" => endpoints
"sharedInbox" => "#{Pleroma.Web.Endpoint.url()}/inbox"
}
} }
|> Map.merge(Utils.make_json_ld_header()) |> Map.merge(Utils.make_json_ld_header())
end end
@ -48,6 +67,8 @@ def render("user.json", %{user: user}) do
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
public_key = :public_key.pem_encode([public_key]) public_key = :public_key.pem_encode([public_key])
endpoints = render("endpoints.json", %{user: user})
%{ %{
"id" => user.ap_id, "id" => user.ap_id,
"type" => "Person", "type" => "Person",
@ -65,9 +86,7 @@ def render("user.json", %{user: user}) do
"owner" => user.ap_id, "owner" => user.ap_id,
"publicKeyPem" => public_key "publicKeyPem" => public_key
}, },
"endpoints" => %{ "endpoints" => endpoints,
"sharedInbox" => "#{Pleroma.Web.Endpoint.url()}/inbox"
},
"icon" => %{ "icon" => %{
"type" => "Image", "type" => "Image",
"url" => User.avatar_url(user) "url" => User.avatar_url(user)
@ -86,7 +105,14 @@ def render("following.json", %{user: user, page: page}) do
query = from(user in query, select: [:ap_id]) query = from(user in query, select: [:ap_id])
following = Repo.all(query) following = Repo.all(query)
collection(following, "#{user.ap_id}/following", page, !user.info.hide_follows) total =
if !user.info.hide_follows do
length(following)
else
0
end
collection(following, "#{user.ap_id}/following", page, !user.info.hide_follows, total)
|> Map.merge(Utils.make_json_ld_header()) |> Map.merge(Utils.make_json_ld_header())
end end
@ -95,10 +121,17 @@ def render("following.json", %{user: user}) do
query = from(user in query, select: [:ap_id]) query = from(user in query, select: [:ap_id])
following = Repo.all(query) following = Repo.all(query)
total =
if !user.info.hide_follows do
length(following)
else
0
end
%{ %{
"id" => "#{user.ap_id}/following", "id" => "#{user.ap_id}/following",
"type" => "OrderedCollection", "type" => "OrderedCollection",
"totalItems" => length(following), "totalItems" => total,
"first" => collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows) "first" => collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows)
} }
|> Map.merge(Utils.make_json_ld_header()) |> Map.merge(Utils.make_json_ld_header())
@ -109,7 +142,14 @@ def render("followers.json", %{user: user, page: page}) do
query = from(user in query, select: [:ap_id]) query = from(user in query, select: [:ap_id])
followers = Repo.all(query) followers = Repo.all(query)
collection(followers, "#{user.ap_id}/followers", page, !user.info.hide_followers) total =
if !user.info.hide_followers do
length(followers)
else
0
end
collection(followers, "#{user.ap_id}/followers", page, !user.info.hide_followers, total)
|> Map.merge(Utils.make_json_ld_header()) |> Map.merge(Utils.make_json_ld_header())
end end
@ -118,19 +158,24 @@ def render("followers.json", %{user: user}) do
query = from(user in query, select: [:ap_id]) query = from(user in query, select: [:ap_id])
followers = Repo.all(query) followers = Repo.all(query)
total =
if !user.info.hide_followers do
length(followers)
else
0
end
%{ %{
"id" => "#{user.ap_id}/followers", "id" => "#{user.ap_id}/followers",
"type" => "OrderedCollection", "type" => "OrderedCollection",
"totalItems" => length(followers), "totalItems" => total,
"first" => collection(followers, "#{user.ap_id}/followers", 1, !user.info.hide_followers) "first" =>
collection(followers, "#{user.ap_id}/followers", 1, !user.info.hide_followers, total)
} }
|> Map.merge(Utils.make_json_ld_header()) |> Map.merge(Utils.make_json_ld_header())
end end
def render("outbox.json", %{user: user, max_id: max_qid}) do def render("outbox.json", %{user: user, max_id: max_qid}) do
# XXX: technically note_count is wrong for this, but it's better than nothing
info = User.user_info(user)
params = %{ params = %{
"limit" => "10" "limit" => "10"
} }
@ -158,7 +203,6 @@ def render("outbox.json", %{user: user, max_id: max_qid}) do
"id" => "#{iri}?max_id=#{max_id}", "id" => "#{iri}?max_id=#{max_id}",
"type" => "OrderedCollectionPage", "type" => "OrderedCollectionPage",
"partOf" => iri, "partOf" => iri,
"totalItems" => info.note_count,
"orderedItems" => collection, "orderedItems" => collection,
"next" => "#{iri}?max_id=#{min_id}" "next" => "#{iri}?max_id=#{min_id}"
} }
@ -167,7 +211,6 @@ def render("outbox.json", %{user: user, max_id: max_qid}) do
%{ %{
"id" => iri, "id" => iri,
"type" => "OrderedCollection", "type" => "OrderedCollection",
"totalItems" => info.note_count,
"first" => page "first" => page
} }
|> Map.merge(Utils.make_json_ld_header()) |> Map.merge(Utils.make_json_ld_header())
@ -205,7 +248,6 @@ def render("inbox.json", %{user: user, max_id: max_qid}) do
"id" => "#{iri}?max_id=#{max_id}", "id" => "#{iri}?max_id=#{max_id}",
"type" => "OrderedCollectionPage", "type" => "OrderedCollectionPage",
"partOf" => iri, "partOf" => iri,
"totalItems" => -1,
"orderedItems" => collection, "orderedItems" => collection,
"next" => "#{iri}?max_id=#{min_id}" "next" => "#{iri}?max_id=#{min_id}"
} }
@ -214,7 +256,6 @@ def render("inbox.json", %{user: user, max_id: max_qid}) do
%{ %{
"id" => iri, "id" => iri,
"type" => "OrderedCollection", "type" => "OrderedCollection",
"totalItems" => -1,
"first" => page "first" => page
} }
|> Map.merge(Utils.make_json_ld_header()) |> Map.merge(Utils.make_json_ld_header())

View File

@ -3,7 +3,11 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.CommonAPI do defmodule Pleroma.Web.CommonAPI do
alias Pleroma.{User, Repo, Activity, Object} alias Pleroma.User
alias Pleroma.Repo
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.ThreadMute
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Formatter alias Pleroma.Formatter
@ -216,4 +220,27 @@ def unpin(id_or_ap_id, user) do
{:error, "Could not unpin"} {:error, "Could not unpin"}
end end
end end
def add_mute(user, activity) do
with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]) do
{:ok, activity}
else
{:error, _} -> {:error, "conversation is already muted"}
end
end
def remove_mute(user, activity) do
ThreadMute.remove_mute(user.id, activity.data["context"])
{:ok, activity}
end
def thread_muted?(%{id: nil} = _user, _activity), do: false
def thread_muted?(user, activity) do
with [] <- ThreadMute.check_muted(user.id, activity.data["context"]) do
false
else
_ -> true
end
end
end end

View File

@ -5,12 +5,15 @@
defmodule Pleroma.Web.CommonAPI.Utils do defmodule Pleroma.Web.CommonAPI.Utils do
alias Calendar.Strftime alias Calendar.Strftime
alias Comeonin.Pbkdf2 alias Comeonin.Pbkdf2
alias Pleroma.{Activity, Formatter, Object, Repo} alias Pleroma.Activity
alias Pleroma.Formatter
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web alias Pleroma.Web
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Endpoint alias Pleroma.Web.Endpoint
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy
alias Pleroma.Web.ActivityPub.Utils
# This is a hack for twidere. # This is a hack for twidere.
def get_by_id_or_ap_id(id) do def get_by_id_or_ap_id(id) do
@ -95,7 +98,7 @@ def make_content_html(
def make_context(%Activity{data: %{"context" => context}}), do: context def make_context(%Activity{data: %{"context" => context}}), do: context
def make_context(_), do: Utils.generate_context_id() def make_context(_), do: Utils.generate_context_id()
def maybe_add_attachments(text, _attachments, _no_links = true), do: text def maybe_add_attachments(text, _attachments, true = _no_links), do: text
def maybe_add_attachments(text, attachments, _no_links) do def maybe_add_attachments(text, attachments, _no_links) do
add_attachments(text, attachments) add_attachments(text, attachments)

View File

@ -4,15 +4,19 @@
defmodule Pleroma.Web.Federator do defmodule Pleroma.Web.Federator do
use GenServer use GenServer
alias Pleroma.User
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Web.{WebFinger, Websub, Salmon} alias Pleroma.User
alias Pleroma.Web.Federator.RetryQueue alias Pleroma.Web.WebFinger
alias Pleroma.Web.Websub
alias Pleroma.Web.Salmon
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Federator.RetryQueue
alias Pleroma.Web.OStatus alias Pleroma.Web.OStatus
require Logger require Logger
@websub Application.get_env(:pleroma, :websub) @websub Application.get_env(:pleroma, :websub)
@ -25,7 +29,7 @@ def init(args) do
def start_link do def start_link do
spawn(fn -> spawn(fn ->
# 1 minute # 1 minute
Process.sleep(1000 * 60 * 1) Process.sleep(1000 * 60)
enqueue(:refresh_subscriptions, nil) enqueue(:refresh_subscriptions, nil)
end) end)
@ -196,8 +200,7 @@ def handle_cast({:enqueue, type, payload, _priority}, state) do
{:noreply, %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}}} {:noreply, %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}}}
end end
def handle_cast(m, state) do def handle_cast(_, state) do
IO.inspect("Unknown: #{inspect(m)}, #{inspect(state)}")
{:noreply, state} {:noreply, state}
end end

View File

@ -5,8 +5,9 @@
# https://tools.ietf.org/html/draft-cavage-http-signatures-08 # https://tools.ietf.org/html/draft-cavage-http-signatures-08
defmodule Pleroma.Web.HTTPSignatures do defmodule Pleroma.Web.HTTPSignatures do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
require Logger require Logger
def split_signature(sig) do def split_signature(sig) do

View File

@ -4,23 +4,31 @@
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.{Repo, Object, Activity, User, Notification, Stats} alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Filter
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.Stats
alias Pleroma.User
alias Pleroma.Web alias Pleroma.Web
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MediaProxy
alias Pleroma.Web.Push
alias Push.Subscription
alias Pleroma.Web.MastodonAPI.{ alias Pleroma.Web.MastodonAPI.AccountView
StatusView, alias Pleroma.Web.MastodonAPI.FilterView
AccountView, alias Pleroma.Web.MastodonAPI.ListView
MastodonView, alias Pleroma.Web.MastodonAPI.MastodonView
ListView, alias Pleroma.Web.MastodonAPI.PushSubscriptionView
FilterView, alias Pleroma.Web.MastodonAPI.StatusView
PushSubscriptionView
}
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.{Authorization, Token, App} alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.MediaProxy alias Pleroma.Web.OAuth.Token
import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2] import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
import Ecto.Query import Ecto.Query
@ -140,7 +148,7 @@ def user(%{assigns: %{user: for_user}} = conn, %{"id" => id}) do
@mastodon_api_level "2.5.0" @mastodon_api_level "2.5.0"
def masto_instance(conn, _params) do def masto_instance(conn, _params) do
instance = Pleroma.Config.get(:instance) instance = Config.get(:instance)
response = %{ response = %{
uri: Web.base_url(), uri: Web.base_url(),
@ -236,7 +244,8 @@ def home_timeline(%{assigns: %{user: user}} = conn, params) do
|> Map.put("user", user) |> Map.put("user", user)
activities = activities =
ActivityPub.fetch_activities([user.ap_id | user.following], params) [user.ap_id | user.following]
|> ActivityPub.fetch_activities(params)
|> ActivityPub.contain_timeline(user) |> ActivityPub.contain_timeline(user)
|> Enum.reverse() |> Enum.reverse()
@ -249,14 +258,12 @@ def home_timeline(%{assigns: %{user: user}} = conn, params) do
def public_timeline(%{assigns: %{user: user}} = conn, params) do def public_timeline(%{assigns: %{user: user}} = conn, params) do
local_only = params["local"] in [true, "True", "true", "1"] local_only = params["local"] in [true, "True", "true", "1"]
params = activities =
params params
|> Map.put("type", ["Create", "Announce"]) |> Map.put("type", ["Create", "Announce"])
|> Map.put("local_only", local_only) |> Map.put("local_only", local_only)
|> Map.put("blocking_user", user) |> Map.put("blocking_user", user)
|> ActivityPub.fetch_public_activities()
activities =
ActivityPub.fetch_public_activities(params)
|> Enum.reverse() |> Enum.reverse()
conn conn
@ -325,6 +332,7 @@ def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
as: :activity as: :activity
) )
|> Enum.reverse(), |> Enum.reverse(),
# credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart
descendants: descendants:
StatusView.render( StatusView.render(
"index.json", "index.json",
@ -333,6 +341,7 @@ def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
as: :activity as: :activity
) )
|> Enum.reverse() |> Enum.reverse()
# credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart
} }
json(conn, result) json(conn, result)
@ -456,13 +465,37 @@ def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
end end
end end
def mute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
activity = Activity.get_by_id(id)
with {:ok, activity} <- CommonAPI.add_mute(user, activity) do
conn
|> put_view(StatusView)
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
else
{:error, reason} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(:bad_request, Jason.encode!(%{"error" => reason}))
end
end
def unmute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
activity = Activity.get_by_id(id)
with {:ok, activity} <- CommonAPI.remove_mute(user, activity) do
conn
|> put_view(StatusView)
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
end
end
def notifications(%{assigns: %{user: user}} = conn, params) do def notifications(%{assigns: %{user: user}} = conn, params) do
notifications = Notification.for_user(user, params) notifications = Notification.for_user(user, params)
result = result =
Enum.map(notifications, fn x -> notifications
render_notification(user, x) |> Enum.map(fn x -> render_notification(user, x) end)
end)
|> Enum.filter(& &1) |> Enum.filter(& &1)
conn conn
@ -591,7 +624,7 @@ def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do
[] []
|> Enum.map(&String.downcase(&1)) |> Enum.map(&String.downcase(&1))
query_params = activities =
params params
|> Map.put("type", "Create") |> Map.put("type", "Create")
|> Map.put("local_only", local_only) |> Map.put("local_only", local_only)
@ -599,9 +632,7 @@ def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do
|> Map.put("tag", tags) |> Map.put("tag", tags)
|> Map.put("tag_all", tag_all) |> Map.put("tag_all", tag_all)
|> Map.put("tag_reject", tag_reject) |> Map.put("tag_reject", tag_reject)
|> ActivityPub.fetch_public_activities()
activities =
ActivityPub.fetch_public_activities(query_params)
|> Enum.reverse() |> Enum.reverse()
conn conn
@ -701,7 +732,7 @@ def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
{:ok, _activity} <- ActivityPub.follow(follower, followed), {:ok, _activity} <- ActivityPub.follow(follower, followed),
{:ok, follower, followed} <- {:ok, follower, followed} <-
User.wait_and_refresh( User.wait_and_refresh(
Pleroma.Config.get([:activitypub, :follow_handshake_timeout]), Config.get([:activitypub, :follow_handshake_timeout]),
follower, follower,
followed followed
) do ) do
@ -830,7 +861,8 @@ def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
tags_path = Web.base_url() <> "/tag/" tags_path = Web.base_url() <> "/tag/"
tags = tags =
String.split(query) query
|> String.split()
|> Enum.uniq() |> Enum.uniq()
|> Enum.filter(fn tag -> String.starts_with?(tag, "#") end) |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
|> Enum.map(fn tag -> String.slice(tag, 1..-1) end) |> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
@ -852,7 +884,8 @@ def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
statuses = status_search(user, query) statuses = status_search(user, query)
tags = tags =
String.split(query) query
|> String.split()
|> Enum.uniq() |> Enum.uniq()
|> Enum.filter(fn tag -> String.starts_with?(tag, "#") end) |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
|> Enum.map(fn tag -> String.slice(tag, 1..-1) end) |> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
@ -876,14 +909,12 @@ def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) d
end end
def favourites(%{assigns: %{user: user}} = conn, params) do def favourites(%{assigns: %{user: user}} = conn, params) do
params = activities =
params params
|> Map.put("type", "Create") |> Map.put("type", "Create")
|> Map.put("favorited_by", user.ap_id) |> Map.put("favorited_by", user.ap_id)
|> Map.put("blocking_user", user) |> Map.put("blocking_user", user)
|> ActivityPub.fetch_public_activities()
activities =
ActivityPub.fetch_public_activities(params)
|> Enum.reverse() |> Enum.reverse()
conn conn
@ -999,12 +1030,10 @@ def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params)
# we must filter the following list for the user to avoid leaking statuses the user # we must filter the following list for the user to avoid leaking statuses the user
# does not actually have permission to see (for more info, peruse security issue #270). # does not actually have permission to see (for more info, peruse security issue #270).
following_to = activities =
following following
|> Enum.filter(fn x -> x in user.following end) |> Enum.filter(fn x -> x in user.following end)
|> ActivityPub.fetch_activities_bounded(following, params)
activities =
ActivityPub.fetch_activities_bounded(following_to, following, params)
|> Enum.reverse() |> Enum.reverse()
conn conn
@ -1026,7 +1055,7 @@ def index(%{assigns: %{user: user}} = conn, _params) do
if user && token do if user && token do
mastodon_emoji = mastodonized_emoji() mastodon_emoji = mastodonized_emoji()
limit = Pleroma.Config.get([:instance, :limit]) limit = Config.get([:instance, :limit])
accounts = accounts =
Map.put(%{}, user.id, AccountView.render("account.json", %{user: user, for: user})) Map.put(%{}, user.id, AccountView.render("account.json", %{user: user, for: user}))
@ -1050,8 +1079,8 @@ def index(%{assigns: %{user: user}} = conn, _params) do
max_toot_chars: limit max_toot_chars: limit
}, },
rights: %{ rights: %{
delete_others_notice: !!user.info.is_moderator, delete_others_notice: present?(user.info.is_moderator),
admin: !!user.info.is_admin admin: present?(user.info.is_admin)
}, },
compose: %{ compose: %{
me: "#{user.id}", me: "#{user.id}",
@ -1247,7 +1276,7 @@ def render_notification(user, %{id: id, activity: activity, inserted_at: created
end end
def get_filters(%{assigns: %{user: user}} = conn, _) do def get_filters(%{assigns: %{user: user}} = conn, _) do
filters = Pleroma.Filter.get_filters(user) filters = Filter.get_filters(user)
res = FilterView.render("filters.json", filters: filters) res = FilterView.render("filters.json", filters: filters)
json(conn, res) json(conn, res)
end end
@ -1256,7 +1285,7 @@ def create_filter(
%{assigns: %{user: user}} = conn, %{assigns: %{user: user}} = conn,
%{"phrase" => phrase, "context" => context} = params %{"phrase" => phrase, "context" => context} = params
) do ) do
query = %Pleroma.Filter{ query = %Filter{
user_id: user.id, user_id: user.id,
phrase: phrase, phrase: phrase,
context: context, context: context,
@ -1265,13 +1294,13 @@ def create_filter(
# expires_at # expires_at
} }
{:ok, response} = Pleroma.Filter.create(query) {:ok, response} = Filter.create(query)
res = FilterView.render("filter.json", filter: response) res = FilterView.render("filter.json", filter: response)
json(conn, res) json(conn, res)
end end
def get_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do def get_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
filter = Pleroma.Filter.get(filter_id, user) filter = Filter.get(filter_id, user)
res = FilterView.render("filter.json", filter: filter) res = FilterView.render("filter.json", filter: filter)
json(conn, res) json(conn, res)
end end
@ -1280,7 +1309,7 @@ def update_filter(
%{assigns: %{user: user}} = conn, %{assigns: %{user: user}} = conn,
%{"phrase" => phrase, "context" => context, "id" => filter_id} = params %{"phrase" => phrase, "context" => context, "id" => filter_id} = params
) do ) do
query = %Pleroma.Filter{ query = %Filter{
user_id: user.id, user_id: user.id,
filter_id: filter_id, filter_id: filter_id,
phrase: phrase, phrase: phrase,
@ -1290,32 +1319,32 @@ def update_filter(
# expires_at # expires_at
} }
{:ok, response} = Pleroma.Filter.update(query) {:ok, response} = Filter.update(query)
res = FilterView.render("filter.json", filter: response) res = FilterView.render("filter.json", filter: response)
json(conn, res) json(conn, res)
end end
def delete_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do def delete_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
query = %Pleroma.Filter{ query = %Filter{
user_id: user.id, user_id: user.id,
filter_id: filter_id filter_id: filter_id
} }
{:ok, _} = Pleroma.Filter.delete(query) {:ok, _} = Filter.delete(query)
json(conn, %{}) json(conn, %{})
end end
def create_push_subscription(%{assigns: %{user: user, token: token}} = conn, params) do def create_push_subscription(%{assigns: %{user: user, token: token}} = conn, params) do
true = Pleroma.Web.Push.enabled() true = Push.enabled()
Pleroma.Web.Push.Subscription.delete_if_exists(user, token) Subscription.delete_if_exists(user, token)
{:ok, subscription} = Pleroma.Web.Push.Subscription.create(user, token, params) {:ok, subscription} = Subscription.create(user, token, params)
view = PushSubscriptionView.render("push_subscription.json", subscription: subscription) view = PushSubscriptionView.render("push_subscription.json", subscription: subscription)
json(conn, view) json(conn, view)
end end
def get_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do def get_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do
true = Pleroma.Web.Push.enabled() true = Push.enabled()
subscription = Pleroma.Web.Push.Subscription.get(user, token) subscription = Subscription.get(user, token)
view = PushSubscriptionView.render("push_subscription.json", subscription: subscription) view = PushSubscriptionView.render("push_subscription.json", subscription: subscription)
json(conn, view) json(conn, view)
end end
@ -1324,15 +1353,15 @@ def update_push_subscription(
%{assigns: %{user: user, token: token}} = conn, %{assigns: %{user: user, token: token}} = conn,
params params
) do ) do
true = Pleroma.Web.Push.enabled() true = Push.enabled()
{:ok, subscription} = Pleroma.Web.Push.Subscription.update(user, token, params) {:ok, subscription} = Subscription.update(user, token, params)
view = PushSubscriptionView.render("push_subscription.json", subscription: subscription) view = PushSubscriptionView.render("push_subscription.json", subscription: subscription)
json(conn, view) json(conn, view)
end end
def delete_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do def delete_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do
true = Pleroma.Web.Push.enabled() true = Push.enabled()
{:ok, _response} = Pleroma.Web.Push.Subscription.delete(user, token) {:ok, _response} = Subscription.delete(user, token)
json(conn, %{}) json(conn, %{})
end end
@ -1343,17 +1372,21 @@ def errors(conn, _) do
end end
def suggestions(%{assigns: %{user: user}} = conn, _) do def suggestions(%{assigns: %{user: user}} = conn, _) do
suggestions = Pleroma.Config.get(:suggestions) suggestions = Config.get(:suggestions)
if Keyword.get(suggestions, :enabled, false) do if Keyword.get(suggestions, :enabled, false) do
api = Keyword.get(suggestions, :third_party_engine, "") api = Keyword.get(suggestions, :third_party_engine, "")
timeout = Keyword.get(suggestions, :timeout, 5000) timeout = Keyword.get(suggestions, :timeout, 5000)
limit = Keyword.get(suggestions, :limit, 23) limit = Keyword.get(suggestions, :limit, 23)
host = Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) host = Config.get([Pleroma.Web.Endpoint, :url, :host])
user = user.nickname user = user.nickname
url = String.replace(api, "{{host}}", host) |> String.replace("{{user}}", user)
url =
api
|> String.replace("{{host}}", host)
|> String.replace("{{user}}", user)
with {:ok, %{status: 200, body: body}} <- with {:ok, %{status: 200, body: body}} <-
@httpoison.get( @httpoison.get(
@ -1366,8 +1399,9 @@ def suggestions(%{assigns: %{user: user}} = conn, _) do
] ]
), ),
{:ok, data} <- Jason.decode(body) do {:ok, data} <- Jason.decode(body) do
data2 = data =
Enum.slice(data, 0, limit) data
|> Enum.slice(0, limit)
|> Enum.map(fn x -> |> Enum.map(fn x ->
Map.put( Map.put(
x, x,
@ -1386,7 +1420,7 @@ def suggestions(%{assigns: %{user: user}} = conn, _) do
end) end)
conn conn
|> json(data2) |> json(data)
else else
e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}") e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
end end
@ -1429,4 +1463,8 @@ def try_render(conn, _, _) do
|> put_status(501) |> put_status(501)
|> json(%{error: "Can't display this activity"}) |> json(%{error: "Can't display this activity"})
end end
defp present?(nil), do: false
defp present?(false), do: false
defp present?(_), do: true
end end

View File

@ -4,11 +4,12 @@
defmodule Pleroma.Web.MastodonAPI.AccountView do defmodule Pleroma.Web.MastodonAPI.AccountView do
use Pleroma.Web, :view use Pleroma.Web, :view
alias Pleroma.User
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MediaProxy
alias Pleroma.HTML alias Pleroma.HTML
alias Pleroma.User
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MediaProxy
def render("accounts.json", %{users: users} = opts) do def render("accounts.json", %{users: users} = opts) do
users users

View File

@ -4,8 +4,8 @@
defmodule Pleroma.Web.MastodonAPI.FilterView do defmodule Pleroma.Web.MastodonAPI.FilterView do
use Pleroma.Web, :view use Pleroma.Web, :view
alias Pleroma.Web.MastodonAPI.FilterView
alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.FilterView
def render("filters.json", %{filters: filters} = opts) do def render("filters.json", %{filters: filters} = opts) do
render_many(filters, FilterView, "filter.json", opts) render_many(filters, FilterView, "filter.json", opts)

View File

@ -9,10 +9,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
alias Pleroma.HTML alias Pleroma.HTML
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MediaProxy
alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.MediaProxy
# TODO: Add cached version. # TODO: Add cached version.
defp get_replied_to_activities(activities) do defp get_replied_to_activities(activities) do
@ -160,7 +161,7 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
reblogged: present?(repeated), reblogged: present?(repeated),
favourited: present?(favorited), favourited: present?(favorited),
bookmarked: present?(bookmarked), bookmarked: present?(bookmarked),
muted: false, muted: CommonAPI.thread_muted?(user, activity),
pinned: pinned?(activity, user), pinned: pinned?(activity, user),
sensitive: sensitive, sensitive: sensitive,
spoiler_text: object["summary"] || "", spoiler_text: object["summary"] || "",

View File

@ -6,7 +6,8 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
require Logger require Logger
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
alias Pleroma.{User, Repo} alias Pleroma.Repo
alias Pleroma.User
@behaviour :cowboy_websocket_handler @behaviour :cowboy_websocket_handler

View File

@ -4,11 +4,12 @@
defmodule Pleroma.Web.MediaProxy.MediaProxyController do defmodule Pleroma.Web.MediaProxy.MediaProxyController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.{Web.MediaProxy, ReverseProxy} alias Pleroma.ReverseProxy
alias Pleroma.Web.MediaProxy
@default_proxy_opts [max_body_length: 25 * 1_048_576, http: [follow_redirect: true]] @default_proxy_opts [max_body_length: 25 * 1_048_576, http: [follow_redirect: true]]
def remote(conn, params = %{"sig" => sig64, "url" => url64}) do def remote(conn, %{"sig" => sig64, "url" => url64} = params) do
with config <- Pleroma.Config.get([:media_proxy], []), with config <- Pleroma.Config.get([:media_proxy], []),
true <- Keyword.get(config, :enabled, false), true <- Keyword.get(config, :enabled, false),
{:ok, url} <- MediaProxy.decode_url(sig64, url64), {:ok, url} <- MediaProxy.decode_url(sig64, url64),

View File

@ -9,7 +9,7 @@ def url(nil), do: nil
def url(""), do: nil def url(""), do: nil
def url(url = "/" <> _), do: url def url("/" <> _ = url), do: url
def url(url) do def url(url) do
config = Application.get_env(:pleroma, :media_proxy, []) config = Application.get_env(:pleroma, :media_proxy, [])
@ -19,11 +19,16 @@ def url(url) do
else else
secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base] secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base]
# Must preserve `%2F` for compatibility with S3 (https://git.pleroma.social/pleroma/pleroma/issues/580)
replacement = get_replacement(url, ":2F:")
# The URL is url-decoded and encoded again to ensure it is correctly encoded and not twice. # The URL is url-decoded and encoded again to ensure it is correctly encoded and not twice.
base64 = base64 =
url url
|> String.replace("%2F", replacement)
|> URI.decode() |> URI.decode()
|> URI.encode() |> URI.encode()
|> String.replace(replacement, "%2F")
|> Base.url_encode64(@base64_opts) |> Base.url_encode64(@base64_opts)
sig = :crypto.hmac(:sha, secret, base64) sig = :crypto.hmac(:sha, secret, base64)
@ -60,4 +65,12 @@ def build_url(sig_base64, url_base64, filename \\ nil) do
|> Enum.filter(fn value -> value end) |> Enum.filter(fn value -> value end)
|> Path.join() |> Path.join()
end end
defp get_replacement(url, replacement) do
if String.contains?(url, replacement) do
get_replacement(url, replacement <> replacement)
else
replacement
end
end
end end

View File

@ -3,10 +3,12 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Metadata.Providers.OpenGraph do defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
alias Pleroma.Web.Metadata.Providers.Provider alias Pleroma.HTML
alias Pleroma.Formatter
alias Pleroma.User
alias Pleroma.Web.Metadata alias Pleroma.Web.Metadata
alias Pleroma.{HTML, Formatter, User}
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy
alias Pleroma.Web.Metadata.Providers.Provider
@behaviour Provider @behaviour Provider

View File

@ -5,10 +5,11 @@
defmodule Pleroma.Web.Nodeinfo.NodeinfoController do defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Stats
alias Pleroma.Web
alias Pleroma.{User, Repo}
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.Repo
alias Pleroma.Stats
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.ActivityPub.MRF
plug(Pleroma.Web.FederatingPlug) plug(Pleroma.Web.FederatingPlug)
@ -32,7 +33,7 @@ def schemas(conn, _params) do
# returns a nodeinfo 2.0 map, since 2.1 just adds a repository field # returns a nodeinfo 2.0 map, since 2.1 just adds a repository field
# under software. # under software.
def raw_nodeinfo() do def raw_nodeinfo do
instance = Application.get_env(:pleroma, :instance) instance = Application.get_env(:pleroma, :instance)
media_proxy = Application.get_env(:pleroma, :media_proxy) media_proxy = Application.get_env(:pleroma, :media_proxy)
suggestions = Application.get_env(:pleroma, :suggestions) suggestions = Application.get_env(:pleroma, :suggestions)
@ -93,10 +94,8 @@ def raw_nodeinfo() do
Config.get([:mrf_user_allowlist], []) Config.get([:mrf_user_allowlist], [])
|> Enum.into(%{}, fn {k, v} -> {k, length(v)} end) |> Enum.into(%{}, fn {k, v} -> {k, length(v)} end)
mrf_transparency = Keyword.get(instance, :mrf_transparency)
federation_response = federation_response =
if mrf_transparency do if Keyword.get(instance, :mrf_transparency) do
%{ %{
mrf_policies: mrf_policies, mrf_policies: mrf_policies,
mrf_simple: mrf_simple, mrf_simple: mrf_simple,

View File

@ -4,7 +4,7 @@
defmodule Pleroma.Web.OAuth.App do defmodule Pleroma.Web.OAuth.App do
use Ecto.Schema use Ecto.Schema
import Ecto.{Changeset} import Ecto.Changeset
schema "apps" do schema "apps" do
field(:client_name, :string) field(:client_name, :string)
@ -25,8 +25,14 @@ def register_changeset(struct, params \\ %{}) do
if changeset.valid? do if changeset.valid? do
changeset changeset
|> put_change(:client_id, :crypto.strong_rand_bytes(32) |> Base.url_encode64()) |> put_change(
|> put_change(:client_secret, :crypto.strong_rand_bytes(32) |> Base.url_encode64()) :client_id,
:crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
)
|> put_change(
:client_secret,
:crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
)
else else
changeset changeset
end end

View File

@ -5,10 +5,13 @@
defmodule Pleroma.Web.OAuth.Authorization do defmodule Pleroma.Web.OAuth.Authorization do
use Ecto.Schema use Ecto.Schema
alias Pleroma.{User, Repo} alias Pleroma.User
alias Pleroma.Web.OAuth.{Authorization, App} alias Pleroma.Repo
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.App
import Ecto.{Changeset, Query} import Ecto.Changeset
import Ecto.Query
schema "oauth_authorizations" do schema "oauth_authorizations" do
field(:token, :string) field(:token, :string)
@ -23,7 +26,7 @@ defmodule Pleroma.Web.OAuth.Authorization do
def create_authorization(%App{} = app, %User{} = user, scopes \\ nil) do def create_authorization(%App{} = app, %User{} = user, scopes \\ nil) do
scopes = scopes || app.scopes scopes = scopes || app.scopes
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64() token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
authorization = %Authorization{ authorization = %Authorization{
token: token, token: token,

View File

@ -5,8 +5,11 @@
defmodule Pleroma.Web.OAuth.OAuthController do defmodule Pleroma.Web.OAuth.OAuthController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Web.OAuth.{Authorization, Token, App} alias Pleroma.Web.OAuth.Authorization
alias Pleroma.{Repo, User} alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.OAuth.App
alias Pleroma.Repo
alias Pleroma.User
alias Comeonin.Pbkdf2 alias Comeonin.Pbkdf2
import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2] import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
@ -186,7 +189,7 @@ defp fix_padding(token) do
token token
|> URI.decode() |> URI.decode()
|> Base.url_decode64!(padding: false) |> Base.url_decode64!(padding: false)
|> Base.url_encode64() |> Base.url_encode64(padding: false)
end end
defp get_app_from_request(conn, params) do defp get_app_from_request(conn, params) do

View File

@ -7,8 +7,11 @@ defmodule Pleroma.Web.OAuth.Token do
import Ecto.Query import Ecto.Query
alias Pleroma.{User, Repo} alias Pleroma.User
alias Pleroma.Web.OAuth.{Token, App, Authorization} alias Pleroma.Repo
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Authorization
schema "oauth_tokens" do schema "oauth_tokens" do
field(:token, :string) field(:token, :string)
@ -30,8 +33,8 @@ def exchange_token(app, auth) do
def create_token(%App{} = app, %User{} = user, scopes \\ nil) do def create_token(%App{} = app, %User{} = user, scopes \\ nil) do
scopes = scopes || app.scopes scopes = scopes || app.scopes
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64() token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
refresh_token = :crypto.strong_rand_bytes(32) |> Base.url_encode64() refresh_token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
token = %Token{ token = %Token{
token: token, token: token,

View File

@ -3,8 +3,11 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OStatus.ActivityRepresenter do defmodule Pleroma.Web.OStatus.ActivityRepresenter do
alias Pleroma.{Activity, User, Object} alias Pleroma.Activity
alias Pleroma.User
alias Pleroma.Object
alias Pleroma.Web.OStatus.UserRepresenter alias Pleroma.Web.OStatus.UserRepresenter
require Logger require Logger
defp get_href(id) do defp get_href(id) do

View File

@ -3,10 +3,11 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OStatus.FeedRepresenter do defmodule Pleroma.Web.OStatus.FeedRepresenter do
alias Pleroma.Web.OStatus
alias Pleroma.Web.OStatus.{UserRepresenter, ActivityRepresenter}
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.OStatus
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy
alias Pleroma.Web.OStatus.ActivityRepresenter
alias Pleroma.Web.OStatus.UserRepresenter
def to_simple_form(user, activities, _users) do def to_simple_form(user, activities, _users) do
most_recent_update = most_recent_update =

View File

@ -3,7 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OStatus.FollowHandler do defmodule Pleroma.Web.OStatus.FollowHandler do
alias Pleroma.Web.{XML, OStatus} alias Pleroma.Web.XML
alias Pleroma.Web.OStatus
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.User alias Pleroma.User

View File

@ -4,8 +4,10 @@
defmodule Pleroma.Web.OStatus.NoteHandler do defmodule Pleroma.Web.OStatus.NoteHandler do
require Logger require Logger
alias Pleroma.Web.{XML, OStatus} alias Pleroma.Web.OStatus
alias Pleroma.{Object, Activity} alias Pleroma.Web.XML
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI

View File

@ -3,7 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OStatus.UnfollowHandler do defmodule Pleroma.Web.OStatus.UnfollowHandler do
alias Pleroma.Web.{XML, OStatus} alias Pleroma.Web.XML
alias Pleroma.Web.OStatus
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.User alias Pleroma.User

View File

@ -9,11 +9,19 @@ defmodule Pleroma.Web.OStatus do
import Pleroma.Web.XML import Pleroma.Web.XML
require Logger require Logger
alias Pleroma.{Repo, User, Web, Object, Activity} alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Object
alias Pleroma.Activity
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.{WebFinger, Websub}
alias Pleroma.Web.OStatus.{FollowHandler, UnfollowHandler, NoteHandler, DeleteHandler}
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.WebFinger
alias Pleroma.Web.Websub
alias Pleroma.Web.OStatus.FollowHandler
alias Pleroma.Web.OStatus.UnfollowHandler
alias Pleroma.Web.OStatus.NoteHandler
alias Pleroma.Web.OStatus.DeleteHandler
def is_representable?(%Activity{data: data}) do def is_representable?(%Activity{data: data}) do
object = Object.normalize(data["object"]) object = Object.normalize(data["object"])

View File

@ -5,13 +5,17 @@
defmodule Pleroma.Web.OStatus.OStatusController do defmodule Pleroma.Web.OStatus.OStatusController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.{User, Activity, Object} alias Pleroma.Activity
alias Pleroma.Web.OStatus.{FeedRepresenter, ActivityRepresenter} alias Pleroma.Object
alias Pleroma.Web.{OStatus, Federator} alias Pleroma.User
alias Pleroma.Web.XML
alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.ActivityPubController
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.ActivityPubController
alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.OStatus.ActivityRepresenter
alias Pleroma.Web.OStatus.FeedRepresenter
alias Pleroma.Web.Federator
alias Pleroma.Web.OStatus
alias Pleroma.Web.XML
plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming]) plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming])

View File

@ -5,7 +5,8 @@
defmodule Pleroma.Web.Push do defmodule Pleroma.Web.Push do
use GenServer use GenServer
alias Pleroma.{Repo, User} alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.Push.Subscription alias Pleroma.Web.Push.Subscription
require Logger require Logger

View File

@ -4,8 +4,11 @@
defmodule Pleroma.Web.Push.Subscription do defmodule Pleroma.Web.Push.Subscription do
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Pleroma.{Repo, User}
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.Push.Subscription alias Pleroma.Web.Push.Subscription

View File

@ -3,7 +3,9 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Helpers do defmodule Pleroma.Web.RichMedia.Helpers do
alias Pleroma.{Activity, Object, HTML} alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.HTML
alias Pleroma.Web.RichMedia.Parser alias Pleroma.Web.RichMedia.Parser
def fetch_data_for_activity(%Activity{} = activity) do def fetch_data_for_activity(%Activity{} = activity) do

View File

@ -9,6 +9,13 @@ defmodule Pleroma.Web.RichMedia.Parser do
Pleroma.Web.RichMedia.Parsers.OEmbed Pleroma.Web.RichMedia.Parsers.OEmbed
] ]
@hackney_options [
pool: :media,
timeout: 2_000,
recv_timeout: 2_000,
max_body: 2_000_000
]
def parse(nil), do: {:error, "No URL provided"} def parse(nil), do: {:error, "No URL provided"}
if Mix.env() == :test do if Mix.env() == :test do
@ -28,7 +35,7 @@ def parse(url) do
defp parse_url(url) do defp parse_url(url) do
try do try do
{:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: [pool: :media]) {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options)
html |> maybe_parse() |> clean_parsed_data() |> check_parsed_data() html |> maybe_parse() |> clean_parsed_data() |> check_parsed_data()
rescue rescue

View File

@ -248,6 +248,8 @@ defmodule Pleroma.Web.Router do
post("/statuses/:id/unpin", MastodonAPIController, :unpin_status) post("/statuses/:id/unpin", MastodonAPIController, :unpin_status)
post("/statuses/:id/bookmark", MastodonAPIController, :bookmark_status) post("/statuses/:id/bookmark", MastodonAPIController, :bookmark_status)
post("/statuses/:id/unbookmark", MastodonAPIController, :unbookmark_status) post("/statuses/:id/unbookmark", MastodonAPIController, :unbookmark_status)
post("/statuses/:id/mute", MastodonAPIController, :mute_conversation)
post("/statuses/:id/unmute", MastodonAPIController, :unmute_conversation)
post("/media", MastodonAPIController, :upload) post("/media", MastodonAPIController, :upload)
put("/media/:id", MastodonAPIController, :update_media) put("/media/:id", MastodonAPIController, :update_media)
@ -550,9 +552,8 @@ defmodule Pleroma.Web.Router do
scope "/", Pleroma.Web.ActivityPub do scope "/", Pleroma.Web.ActivityPub do
pipe_through(:activitypub) pipe_through(:activitypub)
post("/users/:nickname/inbox", ActivityPubController, :inbox)
post("/inbox", ActivityPubController, :inbox) post("/inbox", ActivityPubController, :inbox)
post("/users/:nickname/inbox", ActivityPubController, :inbox)
end end
scope "/.well-known", Pleroma.Web do scope "/.well-known", Pleroma.Web do

View File

@ -6,10 +6,12 @@ defmodule Pleroma.Web.Salmon do
@httpoison Application.get_env(:pleroma, :httpoison) @httpoison Application.get_env(:pleroma, :httpoison)
use Bitwise use Bitwise
alias Pleroma.Instances alias Pleroma.Instances
alias Pleroma.User
alias Pleroma.Web.XML alias Pleroma.Web.XML
alias Pleroma.Web.OStatus.ActivityRepresenter alias Pleroma.Web.OStatus.ActivityRepresenter
alias Pleroma.User
require Logger require Logger
def decode(salmon) do def decode(salmon) do

View File

@ -5,7 +5,11 @@
defmodule Pleroma.Web.Streamer do defmodule Pleroma.Web.Streamer do
use GenServer use GenServer
require Logger require Logger
alias Pleroma.{User, Notification, Activity, Object, Repo} alias Pleroma.User
alias Pleroma.Notification
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
@keepalive_interval :timer.seconds(30) @keepalive_interval :timer.seconds(30)

View File

@ -71,6 +71,32 @@
font-weight: 500; font-weight: 500;
font-size: 16px; font-size: 16px;
} }
.alert-danger {
box-sizing: border-box;
width: 100%;
color: #D8000C;
background-color: #FFD2D2;
border-radius: 4px;
border: none;
padding: 10px;
margin-top: 20px;
font-weight: 500;
font-size: 16px;
}
.alert-info {
box-sizing: border-box;
width: 100%;
color: #00529B;
background-color: #BDE5F8;
border-radius: 4px;
border: none;
padding: 10px;
margin-top: 20px;
font-weight: 500;
font-size: 16px;
}
</style> </style>
</head> </head>
<body> <body>

View File

@ -1,5 +1,9 @@
<%= if get_flash(@conn, :info) do %>
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p> <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
<% end %>
<%= if get_flash(@conn, :error) do %>
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p> <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
<% end %>
<h2>OAuth Authorization</h2> <h2>OAuth Authorization</h2>
<%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %> <%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
<%= label f, :name, "Name or email" %> <%= label f, :name, "Name or email" %>

View File

@ -4,14 +4,19 @@
defmodule Pleroma.Web.TwitterAPI.UtilController do defmodule Pleroma.Web.TwitterAPI.UtilController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
require Logger require Logger
alias Comeonin.Pbkdf2
alias Pleroma.Emoji
alias Pleroma.PasswordResetToken
alias Pleroma.User
alias Pleroma.Repo
alias Pleroma.Web alias Pleroma.Web
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.OStatus alias Pleroma.Web.OStatus
alias Pleroma.Web.WebFinger alias Pleroma.Web.WebFinger
alias Pleroma.Web.CommonAPI
alias Comeonin.Pbkdf2
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.{Repo, PasswordResetToken, User, Emoji}
def show_password_reset(conn, %{"token" => token}) do def show_password_reset(conn, %{"token" => token}) do
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}), with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),

View File

@ -2,16 +2,20 @@
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
# FIXME: Remove this module?
# THIS MODULE IS DEPRECATED! DON'T USE IT! # THIS MODULE IS DEPRECATED! DON'T USE IT!
# USE THE Pleroma.Web.TwitterAPI.Views.ActivityView MODULE! # USE THE Pleroma.Web.TwitterAPI.Views.ActivityView MODULE!
defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
use Pleroma.Web.TwitterAPI.Representers.BaseRepresenter use Pleroma.Web.TwitterAPI.Representers.BaseRepresenter
alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter
alias Pleroma.{Activity, User} alias Pleroma.Activity
alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView, ActivityView}
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Formatter alias Pleroma.Formatter
alias Pleroma.HTML alias Pleroma.HTML
alias Pleroma.User
alias Pleroma.Web.TwitterAPI.ActivityView
alias Pleroma.Web.TwitterAPI.TwitterAPI
alias Pleroma.Web.TwitterAPI.UserView
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MastodonAPI.StatusView
defp user_by_ap_id(user_list, ap_id) do defp user_by_ap_id(user_list, ap_id) do

View File

@ -3,8 +3,13 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.TwitterAPI do defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
alias Pleroma.{UserInviteToken, User, Activity, Repo, Object} alias Pleroma.UserInviteToken
alias Pleroma.{UserEmail, Mailer} alias Pleroma.User
alias Pleroma.Activity
alias Pleroma.Repo
alias Pleroma.Object
alias Pleroma.UserEmail
alias Pleroma.Mailer
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.TwitterAPI.UserView alias Pleroma.Web.TwitterAPI.UserView
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI

View File

@ -7,12 +7,19 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
import Pleroma.Web.ControllerHelper, only: [json_response: 3] import Pleroma.Web.ControllerHelper, only: [json_response: 3]
alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView, ActivityView, NotificationView} alias Ecto.Changeset
alias Pleroma.Web.CommonAPI
alias Pleroma.{Repo, Activity, Object, User, Notification}
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Ecto.Changeset alias Pleroma.Web.CommonAPI
alias Pleroma.Web.TwitterAPI.ActivityView
alias Pleroma.Web.TwitterAPI.NotificationView
alias Pleroma.Web.TwitterAPI.TwitterAPI
alias Pleroma.Web.TwitterAPI.UserView
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Notification
alias Pleroma.Repo
alias Pleroma.User
require Logger require Logger

View File

@ -4,19 +4,18 @@
defmodule Pleroma.Web.TwitterAPI.ActivityView do defmodule Pleroma.Web.TwitterAPI.ActivityView do
use Pleroma.Web, :view use Pleroma.Web, :view
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.User
alias Pleroma.Web.TwitterAPI.UserView
alias Pleroma.Web.TwitterAPI.ActivityView
alias Pleroma.Web.TwitterAPI.TwitterAPI
alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Formatter
alias Pleroma.HTML alias Pleroma.HTML
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.Formatter alias Pleroma.User
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.TwitterAPI.ActivityView
alias Pleroma.Web.TwitterAPI.TwitterAPI
alias Pleroma.Web.TwitterAPI.UserView
alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter
import Ecto.Query import Ecto.Query
require Logger require Logger

View File

@ -4,10 +4,11 @@
defmodule Pleroma.Web.TwitterAPI.NotificationView do defmodule Pleroma.Web.TwitterAPI.NotificationView do
use Pleroma.Web, :view use Pleroma.Web, :view
alias Pleroma.{Notification, User} alias Pleroma.Notification
alias Pleroma.User
alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.TwitterAPI.UserView
alias Pleroma.Web.TwitterAPI.ActivityView alias Pleroma.Web.TwitterAPI.ActivityView
alias Pleroma.Web.TwitterAPI.UserView
defp get_user(ap_id, opts) do defp get_user(ap_id, opts) do
cond do cond do

View File

@ -4,11 +4,11 @@
defmodule Pleroma.Web.TwitterAPI.UserView do defmodule Pleroma.Web.TwitterAPI.UserView do
use Pleroma.Web, :view use Pleroma.Web, :view
alias Pleroma.User
alias Pleroma.Formatter alias Pleroma.Formatter
alias Pleroma.HTML
alias Pleroma.User
alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy
alias Pleroma.HTML
def render("show.json", %{user: user = %User{}} = assigns) do def render("show.json", %{user: user = %User{}} = assigns) do
render_one(user, Pleroma.Web.TwitterAPI.UserView, "user.json", assigns) render_one(user, Pleroma.Web.TwitterAPI.UserView, "user.json", assigns)

View File

@ -3,7 +3,7 @@ defmodule Pleroma.Web.UploaderController do
alias Pleroma.Uploaders.Uploader alias Pleroma.Uploaders.Uploader
def callback(conn, params = %{"upload_path" => upload_path}) do def callback(conn, %{"upload_path" => upload_path} = params) do
process_callback(conn, :global.whereis_name({Uploader, upload_path}), params) process_callback(conn, :global.whereis_name({Uploader, upload_path}), params)
end end

View File

@ -24,7 +24,8 @@ def controller do
quote do quote do
use Phoenix.Controller, namespace: Pleroma.Web use Phoenix.Controller, namespace: Pleroma.Web
import Plug.Conn import Plug.Conn
import Pleroma.Web.{Gettext, Router.Helpers} import Pleroma.Web.Gettext
import Pleroma.Web.Router.Helpers
end end
end end
@ -37,7 +38,9 @@ def view do
# Import convenience functions from controllers # Import convenience functions from controllers
import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1] import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
import Pleroma.Web.{ErrorHelpers, Gettext, Router.Helpers} import Pleroma.Web.ErrorHelpers
import Pleroma.Web.Gettext
import Pleroma.Web.Router.Helpers
require Logger require Logger
@ -71,6 +74,7 @@ def safe_render_many(collection, view, template, assigns \\ %{}) do
def router do def router do
quote do quote do
use Phoenix.Router use Phoenix.Router
# credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
import Plug.Conn import Plug.Conn
import Phoenix.Controller import Phoenix.Controller
end end
@ -78,6 +82,7 @@ def router do
def channel do def channel do
quote do quote do
# credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
use Phoenix.Channel use Phoenix.Channel
import Pleroma.Web.Gettext import Pleroma.Web.Gettext
end end

View File

@ -5,9 +5,12 @@
defmodule Pleroma.Web.WebFinger do defmodule Pleroma.Web.WebFinger do
@httpoison Application.get_env(:pleroma, :httpoison) @httpoison Application.get_env(:pleroma, :httpoison)
alias Pleroma.{User, XmlBuilder} alias Pleroma.User
alias Pleroma.XmlBuilder
alias Pleroma.Web alias Pleroma.Web
alias Pleroma.Web.{XML, Salmon, OStatus} alias Pleroma.Web.XML
alias Pleroma.Web.Salmon
alias Pleroma.Web.OStatus
require Jason require Jason
require Logger require Logger

View File

@ -4,11 +4,14 @@
defmodule Pleroma.Web.Websub do defmodule Pleroma.Web.Websub do
alias Ecto.Changeset alias Ecto.Changeset
alias Pleroma.Repo
alias Pleroma.Instances alias Pleroma.Instances
alias Pleroma.Web.Websub.{WebsubServerSubscription, WebsubClientSubscription} alias Pleroma.Repo
alias Pleroma.Web.Websub.WebsubServerSubscription
alias Pleroma.Web.Websub.WebsubClientSubscription
alias Pleroma.Web.OStatus.FeedRepresenter alias Pleroma.Web.OStatus.FeedRepresenter
alias Pleroma.Web.{XML, Endpoint, OStatus} alias Pleroma.Web.XML
alias Pleroma.Web.Endpoint
alias Pleroma.Web.OStatus
alias Pleroma.Web.Router.Helpers alias Pleroma.Web.Router.Helpers
require Logger require Logger

View File

@ -5,8 +5,10 @@
defmodule Pleroma.Web.Websub.WebsubController do defmodule Pleroma.Web.Websub.WebsubController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.{Repo, User} alias Pleroma.Repo
alias Pleroma.Web.{Websub, Federator} alias Pleroma.User
alias Pleroma.Web.Websub
alias Pleroma.Web.Federator
alias Pleroma.Web.Websub.WebsubClientSubscription alias Pleroma.Web.Websub.WebsubClientSubscription
require Logger require Logger

View File

@ -0,0 +1,12 @@
defmodule Pleroma.Repo.Migrations.CreateThreadMutes do
use Ecto.Migration
def change do
create table(:thread_mutes) do
add :user_id, references(:users, type: :uuid, on_delete: :delete_all)
add :context, :string
end
create unique_index(:thread_mutes, [:user_id, :context], name: :unique_index)
end
end

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More