@@ -35,6 +35,7 @@ docs-build: | |||
- develop@pleroma/pleroma | |||
variables: | |||
MIX_ENV: dev | |||
PLEROMA_BUILD_ENV: prod | |||
script: | |||
- mix deps.get | |||
- mix compile | |||
@@ -6,14 +6,27 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). | |||
## [Unreleased] | |||
### Added | |||
- MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`) | |||
Configuration: `federation_incoming_replies_max_depth` option | |||
- Mastodon API: Support for the [`tagged` filter](https://github.com/tootsuite/mastodon/pull/9755) in [`GET /api/v1/accounts/:id/statuses`](https://docs.joinmastodon.org/api/rest/accounts/#get-api-v1-accounts-id-statuses) | |||
- Mastodon API, streaming: Add support for passing the token in the `Sec-WebSocket-Protocol` header | |||
- Admin API: Return users' tags when querying reports | |||
- Admin API: Return avatar and display name when querying users | |||
- Admin API: Allow querying user by ID | |||
- Added synchronization of following/followers counters for external users | |||
### Fixed | |||
- Not being able to pin unlisted posts | |||
- Metadata rendering errors resulting in the entire page being inaccessible | |||
- Mastodon API: Handling of search timeouts (`/api/v1/search` and `/api/v2/search`) | |||
- Mastodon API: Embedded relationships not being properly rendered in the Account entity of Status entity | |||
### Changed | |||
- Configuration: OpenGraph and TwitterCard providers enabled by default | |||
- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text | |||
### Changed | |||
- NodeInfo: Return `skipThreadContainment` in `metadata` for the `skip_thread_containment` option | |||
## [1.0.0] - 2019-06-29 | |||
### Security | |||
- Mastodon API: Fix display names not being sanitized | |||
@@ -75,6 +88,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). | |||
- OAuth: added job to clean expired access tokens | |||
- MRF: Support for rejecting reports from specific instances (`mrf_simple`) | |||
- MRF: Support for stripping avatars and banner images from specific instances (`mrf_simple`) | |||
- Ability to reset avatar, profile banner and backgroud | |||
- MRF: Support for running subchains. | |||
- Configuration: `skip_thread_containment` option | |||
- Configuration: `rate_limit` option. See `Pleroma.Plugs.RateLimiter` documentation for details. | |||
@@ -218,6 +218,7 @@ config :pleroma, :instance, | |||
}, | |||
registrations_open: true, | |||
federating: true, | |||
federation_incoming_replies_max_depth: 100, | |||
federation_reachability_timeout_days: 7, | |||
federation_publisher_modules: [ | |||
Pleroma.Web.ActivityPub.Publisher, | |||
@@ -248,7 +249,14 @@ config :pleroma, :instance, | |||
remote_post_retention_days: 90, | |||
skip_thread_containment: true, | |||
limit_to_local_content: :unauthenticated, | |||
dynamic_configuration: false | |||
dynamic_configuration: false, | |||
external_user_synchronization: [ | |||
enabled: false, | |||
# every 2 hours | |||
interval: 60 * 60 * 2, | |||
max_retries: 3, | |||
limit: 500 | |||
] | |||
config :pleroma, :markup, | |||
# XXX - unfortunately, inline images must be enabled by default right now, because | |||
@@ -358,7 +366,11 @@ config :pleroma, :gopher, | |||
port: 9999 | |||
config :pleroma, Pleroma.Web.Metadata, | |||
providers: [Pleroma.Web.Metadata.Providers.RelMe], | |||
providers: [ | |||
Pleroma.Web.Metadata.Providers.OpenGraph, | |||
Pleroma.Web.Metadata.Providers.TwitterCard, | |||
Pleroma.Web.Metadata.Providers.RelMe | |||
], | |||
unfurl_nsfw: false | |||
config :pleroma, :suggestions, | |||
@@ -28,7 +28,8 @@ config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Test | |||
config :pleroma, :instance, | |||
email: "admin@example.com", | |||
notify_email: "noreply@example.com", | |||
skip_thread_containment: false | |||
skip_thread_containment: false, | |||
federating: false | |||
# Configure your database | |||
config :pleroma, Pleroma.Repo, | |||
@@ -76,6 +77,8 @@ IO.puts("RUM enabled: #{rum_enabled}") | |||
config :joken, default_signer: "yU8uHKq+yyAkZ11Hx//jcdacWc8yQ1bxAAGrplzB0Zwwjkp35v0RK9SO8WTPr6QZ" | |||
config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock | |||
try do | |||
import_config "test.secret.exs" | |||
rescue | |||
@@ -38,7 +38,9 @@ Authentication is required and the user must be an admin. | |||
"moderator": bool | |||
}, | |||
"local": bool, | |||
"tags": array | |||
"tags": array, | |||
"avatar": string, | |||
"display_name": string | |||
}, | |||
... | |||
] | |||
@@ -174,13 +176,13 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret | |||
- `nickname` | |||
- `status` BOOLEAN field, false value means deactivation. | |||
## `/api/pleroma/admin/users/:nickname` | |||
## `/api/pleroma/admin/users/:nickname_or_id` | |||
### Retrive the details of a user | |||
- Method: `GET` | |||
- Params: | |||
- `nickname` | |||
- `nickname` or `id` | |||
- Response: | |||
- On failure: `Not found` | |||
- On success: JSON of the user | |||
@@ -331,6 +333,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret | |||
"pleroma": {}, | |||
"sensitive": false | |||
}, | |||
"tags": ["force_unlisted"], | |||
"statuses_count": 3, | |||
"url": "https://pleroma.example.org/users/user", | |||
"username": "user" | |||
@@ -366,6 +369,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret | |||
"pleroma": {}, | |||
"sensitive": false | |||
}, | |||
"tags": ["force_unlisted"], | |||
"statuses_count": 1, | |||
"url": "https://pleroma.example.org/users/lain", | |||
"username": "lain" | |||
@@ -87,6 +87,7 @@ config :pleroma, Pleroma.Emails.Mailer, | |||
* `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`). | |||
* `account_activation_required`: Require users to confirm their emails before signing in. | |||
* `federating`: Enable federation with other instances | |||
* `federation_incoming_replies_max_depth`: Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes. | |||
* `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it. | |||
* `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance | |||
* `rewrite_policy`: Message Rewrite Policy, either one or a list. Here are the ones available by default: | |||
@@ -124,6 +125,12 @@ config :pleroma, Pleroma.Emails.Mailer, | |||
* `skip_thread_containment`: Skip filter out broken threads. The default is `false`. | |||
* `limit_to_local_content`: Limit unauthenticated users to search for local statutes and users only. Possible values: `:unauthenticated`, `:all` and `false`. The default is `:unauthenticated`. | |||
* `dynamic_configuration`: Allow transferring configuration to DB with the subsequent customization from Admin api. | |||
* `external_user_synchronization`: Following/followers counters synchronization settings. | |||
* `enabled`: Enables synchronization | |||
* `interval`: Interval between synchronization. | |||
* `max_retries`: Max rettries for host. After exceeding the limit, the check will not be carried out for users from this host. | |||
* `limit`: Users batch size for processing in one time. | |||
## :logger | |||
@@ -207,7 +207,7 @@ certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ -- | |||
# Add it to the daily cron | |||
echo '#!/bin/sh | |||
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --dry-run --post-hook "systemctl reload nginx" | |||
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --post-hook "systemctl reload nginx" | |||
' > /etc/cron.daily/renew-pleroma-cert | |||
chmod +x /etc/cron.daily/renew-pleroma-cert | |||
@@ -228,7 +228,7 @@ certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ -- | |||
# Add it to the daily cron | |||
echo '#!/bin/sh | |||
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --dry-run --post-hook "rc-service nginx reload" | |||
certbot renew --cert-name yourinstance.tld --webroot -w /var/lib/letsencrypt/ --post-hook "rc-service nginx reload" | |||
' > /etc/periodic/daily/renew-pleroma-cert | |||
chmod +x /etc/periodic/daily/renew-pleroma-cert | |||
@@ -1,6 +1,7 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-onl | |||
defmodule Mix.Tasks.Pleroma.Ecto do | |||
@doc """ | |||
Ensures the given repository's migrations path exists on the file system. | |||
@@ -149,7 +149,7 @@ defmodule Mix.Tasks.Pleroma.Instance do | |||
uploads_dir = | |||
get_option( | |||
options, | |||
:upload_dir, | |||
:uploads_dir, | |||
"What directory should media uploads go in (when using the local uploader)?", | |||
Pleroma.Config.get([Pleroma.Uploaders.Local, :uploads]) | |||
) | |||
@@ -155,7 +155,11 @@ defmodule Pleroma.Application do | |||
start: {Pleroma.Web.Endpoint, :start_link, []}, | |||
type: :supervisor | |||
}, | |||
%{id: Pleroma.Gopher.Server, start: {Pleroma.Gopher.Server, :start_link, []}} | |||
%{id: Pleroma.Gopher.Server, start: {Pleroma.Gopher.Server, :start_link, []}}, | |||
%{ | |||
id: Pleroma.User.SynchronizationWorker, | |||
start: {Pleroma.User.SynchronizationWorker, :start_link, []} | |||
} | |||
] | |||
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html | |||
@@ -44,20 +44,20 @@ defmodule Pleroma.Object do | |||
Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id))) | |||
end | |||
def normalize(_, fetch_remote \\ true) | |||
def normalize(_, fetch_remote \\ true, options \\ []) | |||
# If we pass an Activity to Object.normalize(), we can try to use the preloaded object. | |||
# Use this whenever possible, especially when walking graphs in an O(N) loop! | |||
def normalize(%Object{} = object, _), do: object | |||
def normalize(%Activity{object: %Object{} = object}, _), do: object | |||
def normalize(%Object{} = object, _, _), do: object | |||
def normalize(%Activity{object: %Object{} = object}, _, _), do: object | |||
# A hack for fake activities | |||
def normalize(%Activity{data: %{"object" => %{"fake" => true} = data}}, _) do | |||
def normalize(%Activity{data: %{"object" => %{"fake" => true} = data}}, _, _) do | |||
%Object{id: "pleroma:fake_object_id", data: data} | |||
end | |||
# Catch and log Object.normalize() calls where the Activity's child object is not | |||
# preloaded. | |||
def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}, fetch_remote) do | |||
def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}, fetch_remote, _) do | |||
Logger.debug( | |||
"Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object!" | |||
) | |||
@@ -67,7 +67,7 @@ defmodule Pleroma.Object do | |||
normalize(ap_id, fetch_remote) | |||
end | |||
def normalize(%Activity{data: %{"object" => ap_id}}, fetch_remote) do | |||
def normalize(%Activity{data: %{"object" => ap_id}}, fetch_remote, _) do | |||
Logger.debug( | |||
"Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object!" | |||
) | |||
@@ -78,10 +78,14 @@ defmodule Pleroma.Object do | |||
end | |||
# Old way, try fetching the object through cache. | |||
def normalize(%{"id" => ap_id}, fetch_remote), do: normalize(ap_id, fetch_remote) | |||
def normalize(ap_id, false) when is_binary(ap_id), do: get_cached_by_ap_id(ap_id) | |||
def normalize(ap_id, true) when is_binary(ap_id), do: Fetcher.fetch_object_from_id!(ap_id) | |||
def normalize(_, _), do: nil | |||
def normalize(%{"id" => ap_id}, fetch_remote, _), do: normalize(ap_id, fetch_remote) | |||
def normalize(ap_id, false, _) when is_binary(ap_id), do: get_cached_by_ap_id(ap_id) | |||
def normalize(ap_id, true, options) when is_binary(ap_id) do | |||
Fetcher.fetch_object_from_id!(ap_id, options) | |||
end | |||
def normalize(_, _, _), do: nil | |||
# Owned objects can only be mutated by their owner | |||
def authorize_mutation(%Object{data: %{"actor" => actor}}, %User{ap_id: ap_id}), | |||
@@ -22,7 +22,7 @@ defmodule Pleroma.Object.Fetcher do | |||
# TODO: | |||
# This will create a Create activity, which we need internally at the moment. | |||
def fetch_object_from_id(id) do | |||
def fetch_object_from_id(id, options \\ []) do | |||
if object = Object.get_cached_by_ap_id(id) do | |||
{:ok, object} | |||
else | |||
@@ -38,7 +38,7 @@ defmodule Pleroma.Object.Fetcher do | |||
"object" => data | |||
}, | |||
:ok <- Containment.contain_origin(id, params), | |||
{:ok, activity} <- Transmogrifier.handle_incoming(params), | |||
{:ok, activity} <- Transmogrifier.handle_incoming(params, options), | |||
{:object, _data, %Object{} = object} <- | |||
{:object, data, Object.normalize(activity, false)} do | |||
{:ok, object} | |||
@@ -63,8 +63,8 @@ defmodule Pleroma.Object.Fetcher do | |||
end | |||
end | |||
def fetch_object_from_id!(id) do | |||
with {:ok, object} <- fetch_object_from_id(id) do | |||
def fetch_object_from_id!(id, options \\ []) do | |||
with {:ok, object} <- fetch_object_from_id(id, options) do | |||
object | |||
else | |||
_e -> | |||
@@ -0,0 +1,24 @@ | |||
defmodule Pleroma.ReverseProxy.Client do | |||
@callback request(atom(), String.t(), [tuple()], String.t(), list()) :: | |||
{:ok, pos_integer(), [tuple()], reference() | map()} | |||
| {:ok, pos_integer(), [tuple()]} | |||
| {:ok, reference()} | |||
| {:error, term()} | |||
@callback stream_body(reference() | pid() | map()) :: | |||
{:ok, binary()} | :done | {:error, String.t()} | |||
@callback close(reference() | pid() | map()) :: :ok | |||
def request(method, url, headers, "", opts \\ []) do | |||
client().request(method, url, headers, "", opts) | |||
end | |||
def stream_body(ref), do: client().stream_body(ref) | |||
def close(ref), do: client().close(ref) | |||
defp client do | |||
Pleroma.Config.get([Pleroma.ReverseProxy.Client], :hackney) | |||
end | |||
end |
@@ -146,7 +146,7 @@ defmodule Pleroma.ReverseProxy do | |||
Logger.debug("#{__MODULE__} #{method} #{url} #{inspect(headers)}") | |||
method = method |> String.downcase() |> String.to_existing_atom() | |||
case hackney().request(method, url, headers, "", hackney_opts) do | |||
case client().request(method, url, headers, "", hackney_opts) do | |||
{:ok, code, headers, client} when code in @valid_resp_codes -> | |||
{:ok, code, downcase_headers(headers), client} | |||
@@ -173,7 +173,7 @@ defmodule Pleroma.ReverseProxy do | |||
halt(conn) | |||
{:error, :closed, conn} -> | |||
:hackney.close(client) | |||
client().close(client) | |||
halt(conn) | |||
{:error, error, conn} -> | |||
@@ -181,7 +181,7 @@ defmodule Pleroma.ReverseProxy do | |||
"#{__MODULE__} request to #{url} failed while reading/chunking: #{inspect(error)}" | |||
) | |||
:hackney.close(client) | |||
client().close(client) | |||
halt(conn) | |||
end | |||
end | |||
@@ -196,7 +196,7 @@ defmodule Pleroma.ReverseProxy do | |||
duration, | |||
Keyword.get(opts, :max_read_duration, @max_read_duration) | |||
), | |||
{:ok, data} <- hackney().stream_body(client), | |||
{:ok, data} <- client().stream_body(client), | |||
{:ok, duration} <- increase_read_duration(duration), | |||
sent_so_far = sent_so_far + byte_size(data), | |||
:ok <- body_size_constraint(sent_so_far, Keyword.get(opts, :max_body_size)), | |||
@@ -378,5 +378,5 @@ defmodule Pleroma.ReverseProxy do | |||
{:ok, :no_duration_limit, :no_duration_limit} | |||
end | |||
defp hackney, do: Pleroma.Config.get(:hackney, :hackney) | |||
defp client, do: Pleroma.ReverseProxy.Client | |||
end |
@@ -1,51 +0,0 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Uploaders.Swift.Keystone do | |||
use HTTPoison.Base | |||
def process_url(url) do | |||
Enum.join( | |||
[Pleroma.Config.get!([Pleroma.Uploaders.Swift, :auth_url]), url], | |||
"/" | |||
) | |||
end | |||
def process_response_body(body) do | |||
body | |||
|> Jason.decode!() | |||
end | |||
def get_token do | |||
settings = Pleroma.Config.get(Pleroma.Uploaders.Swift) | |||
username = Keyword.fetch!(settings, :username) | |||
password = Keyword.fetch!(settings, :password) | |||
tenant_id = Keyword.fetch!(settings, :tenant_id) | |||
case post( | |||
"/tokens", | |||
make_auth_body(username, password, tenant_id), | |||
["Content-Type": "application/json"], | |||
hackney: [:insecure] | |||
) do | |||
{:ok, %Tesla.Env{status: 200, body: body}} -> | |||
body["access"]["token"]["id"] | |||
{:ok, %Tesla.Env{status: _}} -> | |||
"" | |||
end | |||
end | |||
def make_auth_body(username, password, tenant) do | |||
Jason.encode!(%{ | |||
:auth => %{ | |||
:passwordCredentials => %{ | |||
:username => username, | |||
:password => password | |||
}, | |||
:tenantId => tenant | |||
} | |||
}) | |||
end | |||
end |
@@ -1,29 +0,0 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Uploaders.Swift.Client do | |||
use HTTPoison.Base | |||
def process_url(url) do | |||
Enum.join( | |||
[Pleroma.Config.get!([Pleroma.Uploaders.Swift, :storage_url]), url], | |||
"/" | |||
) | |||
end | |||
def upload_file(filename, body, content_type) do | |||
token = Pleroma.Uploaders.Swift.Keystone.get_token() | |||
case put("#{filename}", body, "X-Auth-Token": token, "Content-Type": content_type) do | |||
{:ok, %Tesla.Env{status: 201}} -> | |||
{:ok, {:file, filename}} | |||
{:ok, %Tesla.Env{status: 401}} -> | |||
{:error, "Unauthorized, Bad Token"} | |||
{:error, _} -> | |||
{:error, "Swift Upload Error"} | |||
end | |||
end | |||
end |
@@ -1,19 +0,0 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Uploaders.Swift do | |||
@behaviour Pleroma.Uploaders.Uploader | |||
def get_file(name) do | |||
{:ok, {:url, Path.join([Pleroma.Config.get!([__MODULE__, :object_url]), name])}} | |||
end | |||
def put_file(upload) do | |||
Pleroma.Uploaders.Swift.Client.upload_file( | |||
upload.path, | |||
File.read!(upload.tmpfile), | |||
upload.content_type | |||
) | |||
end | |||
end |
@@ -108,15 +108,25 @@ defmodule Pleroma.User do | |||
def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa | |||
def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers" | |||
def user_info(%User{} = user) do | |||
def user_info(%User{} = user, args \\ %{}) do | |||
following_count = | |||
if args[:following_count], do: args[:following_count], else: following_count(user) | |||
follower_count = | |||
if args[:follower_count], do: args[:follower_count], else: user.info.follower_count | |||
%{ | |||
following_count: following_count(user), | |||
note_count: user.info.note_count, | |||
follower_count: user.info.follower_count, | |||
locked: user.info.locked, | |||
confirmation_pending: user.info.confirmation_pending, | |||
default_scope: user.info.default_scope | |||
} | |||
|> Map.put(:following_count, following_count) | |||
|> Map.put(:follower_count, follower_count) | |||
end | |||
def set_info_cache(user, args) do | |||
Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args)) | |||
end | |||
def restrict_deactivated(query) do | |||
@@ -837,15 +847,12 @@ defmodule Pleroma.User do | |||
def mutes?(nil, _), do: false | |||
def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id) | |||
def blocks?(user, %{ap_id: ap_id}) do | |||
blocks = user.info.blocks | |||
domain_blocks = user.info.domain_blocks | |||
def blocks?(%User{info: info} = _user, %{ap_id: ap_id}) do | |||
blocks = info.blocks | |||
domain_blocks = info.domain_blocks | |||
%{host: host} = URI.parse(ap_id) | |||
Enum.member?(blocks, ap_id) || | |||
Enum.any?(domain_blocks, fn domain -> | |||
host == domain | |||
end) | |||
Enum.member?(blocks, ap_id) || Enum.any?(domain_blocks, &(&1 == host)) | |||
end | |||
def subscribed_to?(user, %{ap_id: ap_id}) do | |||
@@ -1004,6 +1011,56 @@ defmodule Pleroma.User do | |||
) | |||
end | |||
@spec sync_follow_counter() :: :ok | |||
def sync_follow_counter, | |||
do: PleromaJobQueue.enqueue(:background, __MODULE__, [:sync_follow_counters]) | |||
@spec perform(:sync_follow_counters) :: :ok | |||
def perform(:sync_follow_counters) do | |||
{:ok, _pid} = Agent.start_link(fn -> %{} end, name: :domain_errors) | |||
config = Pleroma.Config.get([:instance, :external_user_synchronization]) | |||
:ok = sync_follow_counters(config) | |||
Agent.stop(:domain_errors) | |||
end | |||
@spec sync_follow_counters(keyword()) :: :ok | |||
def sync_follow_counters(opts \\ []) do | |||
users = external_users(opts) | |||
if length(users) > 0 do | |||
errors = Agent.get(:domain_errors, fn state -> state end) | |||
{last, updated_errors} = User.Synchronization.call(users, errors, opts) | |||
Agent.update(:domain_errors, fn _state -> updated_errors end) | |||
sync_follow_counters(max_id: last.id, limit: opts[:limit]) | |||
else | |||
:ok | |||
end | |||
end | |||
@spec external_users(keyword()) :: [User.t()] | |||
def external_users(opts \\ []) do | |||
query = | |||
User.Query.build(%{ | |||
external: true, | |||
active: true, | |||
order_by: :id, | |||
select: [:id, :ap_id, :info] | |||
}) | |||
query = | |||
if opts[:max_id], | |||
do: where(query, [u], u.id > ^opts[:max_id]), | |||
else: query | |||
query = | |||
if opts[:limit], | |||
do: limit(query, ^opts[:limit]), | |||
else: query | |||
Repo.all(query) | |||
end | |||
def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers), | |||
do: | |||
PleromaJobQueue.enqueue(:background, __MODULE__, [ | |||
@@ -7,7 +7,7 @@ defmodule Pleroma.User.Query do | |||
User query builder module. Builds query from new query or another user query. | |||
## Example: | |||
query = Pleroma.User.Query(%{nickname: "nickname"}) | |||
query = Pleroma.User.Query.build(%{nickname: "nickname"}) | |||
another_query = Pleroma.User.Query.build(query, %{email: "email@example.com"}) | |||
Pleroma.Repo.all(query) | |||
Pleroma.Repo.all(another_query) | |||
@@ -47,7 +47,10 @@ defmodule Pleroma.User.Query do | |||
friends: User.t(), | |||
recipients_from_activity: [String.t()], | |||
nickname: [String.t()], | |||
ap_id: [String.t()] | |||
ap_id: [String.t()], | |||
order_by: term(), | |||
select: term(), | |||
limit: pos_integer() | |||
} | |||
| %{} | |||
@@ -141,6 +144,18 @@ defmodule Pleroma.User.Query do | |||
where(query, [u], u.ap_id in ^to or fragment("? && ?", u.following, ^to)) | |||
end | |||
defp compose_query({:order_by, key}, query) do | |||
order_by(query, [u], field(u, ^key)) | |||
end | |||
defp compose_query({:select, keys}, query) do | |||
select(query, [u], ^keys) | |||
end | |||
defp compose_query({:limit, limit}, query) do | |||
limit(query, ^limit) | |||
end | |||
defp compose_query(_unsupported_param, query), do: query | |||
defp prepare_tag_criteria(tag, query) do | |||
@@ -43,6 +43,8 @@ defmodule Pleroma.User.Search do | |||
defp search_query(query_string, for_user, following) do | |||
for_user | |||
|> base_query(following) | |||
|> filter_blocked_user(for_user) | |||
|> filter_blocked_domains(for_user) | |||
|> search_subqueries(query_string) | |||
|> union_subqueries | |||
|> distinct_query() | |||
@@ -55,6 +57,25 @@ defmodule Pleroma.User.Search do | |||
defp base_query(_user, false), do: User | |||
defp base_query(user, true), do: User.get_followers_query(user) | |||
defp filter_blocked_user(query, %User{info: %{blocks: blocks}}) | |||
when length(blocks) > 0 do | |||
from(q in query, where: not (q.ap_id in ^blocks)) | |||
end | |||
defp filter_blocked_user(query, _), do: query | |||
defp filter_blocked_domains(query, %User{info: %{domain_blocks: domain_blocks}}) | |||
when length(domain_blocks) > 0 do | |||
domains = Enum.join(domain_blocks, ",") | |||
from( | |||
q in query, | |||
where: fragment("substring(ap_id from '.*://([^/]*)') NOT IN (?)", ^domains) | |||
) | |||
end | |||
defp filter_blocked_domains(query, _), do: query | |||
defp paginate(query, limit, offset) do | |||
from(q in query, limit: ^limit, offset: ^offset) | |||
end | |||
@@ -129,7 +150,7 @@ defmodule Pleroma.User.Search do | |||
@spec fts_search_subquery(User.t() | Ecto.Query.t(), String.t()) :: Ecto.Query.t() | |||
defp fts_search_subquery(query, term) do | |||
processed_query = | |||
term | |||
String.trim_trailing(term, "@" <> local_domain()) | |||
|> String.replace(~r/\W+/, " ") | |||
|> String.trim() | |||
|> String.split() | |||
@@ -171,6 +192,8 @@ defmodule Pleroma.User.Search do | |||
@spec trigram_search_subquery(User.t() | Ecto.Query.t(), String.t()) :: Ecto.Query.t() | |||
defp trigram_search_subquery(query, term) do | |||
term = String.trim_trailing(term, "@" <> local_domain()) | |||
from( | |||
u in query, | |||
select_merge: %{ | |||
@@ -188,4 +211,6 @@ defmodule Pleroma.User.Search do | |||
) | |||
|> User.restrict_deactivated() | |||
end | |||
defp local_domain, do: Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) | |||
end |
@@ -0,0 +1,60 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.User.Synchronization do | |||
alias Pleroma.HTTP | |||
alias Pleroma.User | |||
@spec call([User.t()], map(), keyword()) :: {User.t(), map()} | |||
def call(users, errors, opts \\ []) do | |||
do_call(users, errors, opts) | |||
end | |||
defp do_call([user | []], errors, opts) do | |||
updated = fetch_counters(user, errors, opts) | |||
{user, updated} | |||
end | |||
defp do_call([user | others], errors, opts) do | |||
updated = fetch_counters(user, errors, opts) | |||
do_call(others, updated, opts) | |||
end | |||
defp fetch_counters(user, errors, opts) do | |||
%{host: host} = URI.parse(user.ap_id) | |||
info = %{} | |||
{following, errors} = fetch_counter(user.ap_id <> "/following", host, errors, opts) | |||
info = if following, do: Map.put(info, :following_count, following), else: info | |||
{followers, errors} = fetch_counter(user.ap_id <> "/followers", host, errors, opts) | |||
info = if followers, do: Map.put(info, :follower_count, followers), else: info | |||
User.set_info_cache(user, info) | |||
errors | |||
end | |||
defp available_domain?(domain, errors, opts) do | |||
max_retries = Keyword.get(opts, :max_retries, 3) | |||
not (Map.has_key?(errors, domain) && errors[domain] >= max_retries) | |||
end | |||
defp fetch_counter(url, host, errors, opts) do | |||
with true <- available_domain?(host, errors, opts), | |||
{:ok, %{body: body, status: code}} when code in 200..299 <- | |||
HTTP.get( | |||
url, | |||
[{:Accept, "application/activity+json"}] | |||
), | |||
{:ok, data} <- Jason.decode(body) do | |||
{data["totalItems"], errors} | |||
else | |||
false -> | |||
{nil, errors} | |||
_ -> | |||
{nil, Map.update(errors, host, 1, &(&1 + 1))} | |||
end | |||
end | |||
end |
@@ -0,0 +1,32 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-onl | |||
defmodule Pleroma.User.SynchronizationWorker do | |||
use GenServer | |||
def start_link do | |||
config = Pleroma.Config.get([:instance, :external_user_synchronization]) | |||
if config[:enabled] do | |||
GenServer.start_link(__MODULE__, interval: config[:interval]) | |||
else | |||
:ignore | |||
end | |||
end | |||
def init(opts) do | |||
schedule_next(opts) | |||
{:ok, opts} | |||
end | |||
def handle_info(:sync_follow_counters, opts) do | |||
Pleroma.User.sync_follow_counter() | |||
schedule_next(opts) | |||
{:noreply, opts} | |||
end | |||
defp schedule_next(opts) do | |||
Process.send_after(self(), :sync_follow_counters, opts[:interval]) | |||
end | |||
end |
@@ -14,6 +14,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
alias Pleroma.Web.ActivityPub.ActivityPub | |||
alias Pleroma.Web.ActivityPub.Utils | |||
alias Pleroma.Web.ActivityPub.Visibility | |||
alias Pleroma.Web.Federator | |||
import Ecto.Query | |||
@@ -22,20 +23,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
@doc """ | |||
Modifies an incoming AP object (mastodon format) to our internal format. | |||
""" | |||
def fix_object(object) do | |||
def fix_object(object, options \\ []) do | |||
object | |||
|> fix_actor | |||
|> fix_url | |||
|> fix_attachments | |||
|> fix_context | |||
|> fix_in_reply_to | |||
|> fix_in_reply_to(options) | |||
|> fix_emoji | |||
|> fix_tag | |||
|> fix_content_map | |||
|> fix_likes | |||
|> fix_addressing | |||
|> fix_summary | |||
|> fix_type | |||
|> fix_type(options) | |||
end | |||
def fix_summary(%{"summary" => nil} = object) do | |||
@@ -164,7 +165,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
object | |||
end | |||
def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object) | |||
def fix_in_reply_to(object, options \\ []) | |||
def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options) | |||
when not is_nil(in_reply_to) do | |||
in_reply_to_id = | |||
cond do | |||
@@ -182,28 +185,34 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
"" | |||
end | |||
case get_obj_helper(in_reply_to_id) do | |||
{:ok, replied_object} -> | |||
with %Activity{} = _activity <- | |||
Activity.get_create_by_object_ap_id(replied_object.data["id"]) do | |||
object | |||
|> Map.put("inReplyTo", replied_object.data["id"]) | |||
|> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id) | |||
|> Map.put("conversation", replied_object.data["context"] || object["conversation"]) | |||
|> Map.put("context", replied_object.data["context"] || object["conversation"]) | |||
else | |||
e -> | |||
Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}") | |||
object = Map.put(object, "inReplyToAtomUri", in_reply_to_id) | |||
if Federator.allowed_incoming_reply_depth?(options[:depth]) do | |||
case get_obj_helper(in_reply_to_id, options) do | |||
{:ok, replied_object} -> | |||
with %Activity{} = _activity <- | |||
Activity.get_create_by_object_ap_id(replied_object.data["id"]) do | |||
object | |||
end | |||
|> Map.put("inReplyTo", replied_object.data["id"]) | |||
|> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id) | |||
|> Map.put("conversation", replied_object.data["context"] || object["conversation"]) | |||
|> Map.put("context", replied_object.data["context"] || object["conversation"]) | |||
else | |||
e -> | |||
Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}") | |||
object | |||
end | |||
e -> | |||
Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}") | |||
object | |||
e -> | |||
Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}") | |||
object | |||
end | |||
else | |||
object | |||
end | |||
end | |||
def fix_in_reply_to(object), do: object | |||
def fix_in_reply_to(object, _options), do: object | |||
def fix_context(object) do | |||
context = object["context"] || object["conversation"] || Utils.generate_context_id() | |||
@@ -336,8 +345,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
def fix_content_map(object), do: object | |||
def fix_type(%{"inReplyTo" => reply_id} = object) when is_binary(reply_id) do | |||
reply = Object.normalize(reply_id) | |||
def fix_type(object, options \\ []) | |||
def fix_type(%{"inReplyTo" => reply_id} = object, options) when is_binary(reply_id) do | |||
reply = | |||
if Federator.allowed_incoming_reply_depth?(options[:depth]) do | |||
Object.normalize(reply_id, true) | |||
end | |||
if reply && (reply.data["type"] == "Question" and object["name"]) do | |||
Map.put(object, "type", "Answer") | |||
@@ -346,7 +360,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
end | |||
end | |||
def fix_type(object), do: object | |||
def fix_type(object, _), do: object | |||
defp mastodon_follow_hack(%{"id" => id, "actor" => follower_id}, followed) do | |||
with true <- id =~ "follows", | |||
@@ -374,9 +388,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
end | |||
end | |||
def handle_incoming(data, options \\ []) | |||
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them | |||
# with nil ID. | |||
def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} = data) do | |||
def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} = data, _options) do | |||
with context <- data["context"] || Utils.generate_context_id(), | |||
content <- data["content"] || "", | |||
%User{} = actor <- User.get_cached_by_ap_id(actor), | |||
@@ -409,15 +425,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
end | |||
# disallow objects with bogus IDs | |||
def handle_incoming(%{"id" => nil}), do: :error | |||
def handle_incoming(%{"id" => ""}), do: :error | |||
def handle_incoming(%{"id" => nil}, _options), do: :error | |||
def handle_incoming(%{"id" => ""}, _options), do: :error | |||
# length of https:// = 8, should validate better, but good enough for now. | |||
def handle_incoming(%{"id" => id}) when not (is_binary(id) and length(id) > 8), do: :error | |||
def handle_incoming(%{"id" => id}, _options) when not (is_binary(id) and length(id) > 8), | |||
do: :error | |||
# TODO: validate those with a Ecto scheme | |||
# - tags | |||
# - emoji | |||
def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data) | |||
def handle_incoming( | |||
%{"type" => "Create", "object" => %{"type" => objtype} = object} = data, | |||
options | |||
) | |||
when objtype in ["Article", "Note", "Video", "Page", "Question", "Answer"] do | |||
actor = Containment.get_actor(data) | |||
@@ -427,7 +447,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
with nil <- Activity.get_create_by_object_ap_id(object["id"]), | |||
{:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do | |||
object = fix_object(data["object"]) | |||
options = Keyword.put(options, :depth, (options[:depth] || 0) + 1) | |||
object = fix_object(data["object"], options) | |||
params = %{ | |||
to: data["to"], | |||
@@ -452,7 +473,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
end | |||
def handle_incoming( | |||
%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data | |||
%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data, | |||
_options | |||
) do | |||
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed), | |||
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower), | |||
@@ -503,7 +525,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
end | |||
def handle_incoming( | |||
%{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => _id} = data | |||
%{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => _id} = data, | |||
_options | |||
) do | |||
with actor <- Containment.get_actor(data), | |||
{:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor), | |||
@@ -524,7 +547,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
end | |||
def handle_incoming( | |||
%{"type" => "Reject", "object" => follow_object, "actor" => _actor, "id" => _id} = data | |||
%{"type" => "Reject", "object" => follow_object, "actor" => _actor, "id" => _id} = data, | |||
_options | |||
) do | |||
with actor <- Containment.get_actor(data), | |||
{:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor), | |||
@@ -548,7 +572,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
end | |||
def handle_incoming( | |||
%{"type" => "Like", "object" => object_id, "actor" => _actor, "id" => id} = data | |||
%{"type" => "Like", "object" => object_id, "actor" => _actor, "id" => id} = data, | |||
_options | |||
) do | |||
with actor <- Containment.get_actor(data), | |||
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor), | |||
@@ -561,7 +586,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
end | |||
def handle_incoming( | |||
%{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data | |||
%{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data, | |||
_options | |||
) do | |||
with actor <- Containment.get_actor(data), | |||
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor), | |||
@@ -576,7 +602,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
def handle_incoming( | |||
%{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => actor_id} = | |||
data | |||
data, | |||
_options | |||
) | |||
when object_type in ["Person", "Application", "Service", "Organization"] do | |||
with %User{ap_id: ^actor_id} = actor <- User.get_cached_by_ap_id(object["id"]) do | |||
@@ -614,7 +641,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
# an error or a tombstone. This would allow us to verify that a deletion actually took | |||
# place. | |||
def handle_incoming( | |||
%{"type" => "Delete", "object" => object_id, "actor" => _actor, "id" => _id} = data | |||
%{"type" => "Delete", "object" => object_id, "actor" => _actor, "id" => _id} = data, | |||
_options | |||
) do | |||
object_id = Utils.get_ap_id(object_id) | |||
@@ -635,7 +663,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
"object" => %{"type" => "Announce", "object" => object_id}, | |||
"actor" => _actor, | |||
"id" => id | |||
} = data | |||
} = data, | |||
_options | |||
) do | |||
with actor <- Containment.get_actor(data), | |||
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor), | |||
@@ -653,7 +682,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
"object" => %{"type" => "Follow", "object" => followed}, | |||
"actor" => follower, | |||
"id" => id | |||
} = _data | |||
} = _data, | |||
_options | |||
) do | |||
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed), | |||
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower), | |||
@@ -671,7 +701,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
"object" => %{"type" => "Block", "object" => blocked}, | |||
"actor" => blocker, | |||
"id" => id | |||
} = _data | |||
} = _data, | |||
_options | |||
) do | |||
with true <- Pleroma.Config.get([:activitypub, :accept_blocks]), | |||
%User{local: true} = blocked <- User.get_cached_by_ap_id(blocked), | |||
@@ -685,7 +716,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
end | |||
def handle_incoming( | |||
%{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data | |||
%{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data, | |||
_options | |||
) do | |||
with true <- Pleroma.Config.get([:activitypub, :accept_blocks]), | |||
%User{local: true} = blocked = User.get_cached_by_ap_id(blocked), | |||
@@ -705,7 +737,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
"object" => %{"type" => "Like", "object" => object_id}, | |||
"actor" => _actor, | |||
"id" => id | |||
} = data | |||
} = data, | |||
_options | |||
) do | |||
with actor <- Containment.get_actor(data), | |||
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor), | |||
@@ -717,10 +750,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
end | |||
end | |||
def handle_incoming(_), do: :error | |||
def handle_incoming(_, _), do: :error | |||
def get_obj_helper(id) do | |||
if object = Object.normalize(id), do: {:ok, object}, else: nil | |||
def get_obj_helper(id, options \\ []) do | |||
if object = Object.normalize(id, true, options), do: {:ok, object}, else: nil | |||
end | |||
def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_reply_to) do | |||
@@ -170,14 +170,17 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
Enqueues an activity for federation if it's local | |||
""" | |||
def maybe_federate(%Activity{local: true} = activity) do | |||
priority = | |||
case activity.data["type"] do | |||
"Delete" -> 10 | |||
"Create" -> 1 | |||
_ -> 5 | |||
end | |||
if Pleroma.Config.get!([:instance, :federating]) do | |||
priority = | |||
case activity.data["type"] do | |||
"Delete" -> 10 | |||
"Create" -> 1 | |||
_ -> 5 | |||
end | |||
Pleroma.Web.Federator.publish(activity, priority) | |||
end | |||
Pleroma.Web.Federator.publish(activity, priority) | |||
:ok | |||
end | |||
@@ -74,7 +74,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do | |||
end | |||
def user_show(conn, %{"nickname" => nickname}) do | |||
with %User{} = user <- User.get_cached_by_nickname(nickname) do | |||
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do | |||
conn | |||
|> json(AccountView.render("show.json", %{user: user})) | |||
else | |||
@@ -46,8 +46,10 @@ defmodule Pleroma.Web.AdminAPI.ReportView do | |||
} | |||
end | |||
defp merge_account_views(user) do | |||
defp merge_account_views(%User{} = user) do | |||
Pleroma.Web.MastodonAPI.AccountView.render("account.json", %{user: user}) | |||
|> Map.merge(Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user})) | |||
end | |||
defp merge_account_views(_), do: %{} | |||
end |
@@ -22,6 +22,18 @@ defmodule Pleroma.Web.Federator do | |||
refresh_subscriptions() | |||
end | |||
@doc "Addresses [memory leaks on recursive replies fetching](https://git.pleroma.social/pleroma/pleroma/issues/161)" | |||
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength | |||
def allowed_incoming_reply_depth?(depth) do | |||
max_replies_depth = Pleroma.Config.get([:instance, :federation_incoming_replies_max_depth]) | |||
if max_replies_depth do | |||
(depth || 1) <= max_replies_depth | |||
else | |||
true | |||
end | |||
end | |||
# Client API | |||
def incoming_doc(doc) do | |||
@@ -167,6 +167,69 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
end | |||
end | |||
def update_avatar(%{assigns: %{user: user}} = conn, %{"img" => ""}) do | |||
change = Changeset.change(user, %{avatar: nil}) | |||
{:ok, user} = User.update_and_set_cache(change) | |||
CommonAPI.update(user) | |||
json(conn, %{url: nil}) | |||
end | |||
def update_avatar(%{assigns: %{user: user}} = conn, params) do | |||
{:ok, object} = ActivityPub.upload(params, type: :avatar) | |||
change = Changeset.change(user, %{avatar: object.data}) | |||
{:ok, user} = User.update_and_set_cache(change) | |||
CommonAPI.update(user) | |||
%{"url" => [%{"href" => href} | _]} = object.data | |||
json(conn, %{url: href}) | |||
end | |||
def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do | |||
with new_info <- %{"banner" => %{}}, | |||
info_cng <- User.Info.profile_update(user.info, new_info), | |||
changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng), | |||
{:ok, user} <- User.update_and_set_cache(changeset) do | |||
CommonAPI.update(user) | |||
json(conn, %{url: nil}) | |||
end | |||
end | |||
def update_banner(%{assigns: %{user: user}} = conn, params) do | |||
with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner), | |||
new_info <- %{"banner" => object.data}, | |||
info_cng <- User.Info.profile_update(user.info, new_info), | |||
changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng), | |||
{:ok, user} <- User.update_and_set_cache(changeset) do | |||
CommonAPI.update(user) | |||
%{"url" => [%{"href" => href} | _]} = object.data | |||
json(conn, %{url: href}) | |||
end | |||
end | |||
def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do | |||
with new_info <- %{"background" => %{}}, | |||
info_cng <- User.Info.profile_update(user.info, new_info), | |||
changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng), | |||
{:ok, _user} <- User.update_and_set_cache(changeset) do | |||
json(conn, %{url: nil}) | |||
end | |||
end | |||
def update_background(%{assigns: %{user: user}} = conn, params) do | |||
with {:ok, object} <- ActivityPub.upload(params, type: :background), | |||
new_info <- %{"background" => object.data}, | |||
info_cng <- User.Info.profile_update(user.info, new_info), | |||
changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng), | |||
{:ok, _user} <- User.update_and_set_cache(changeset) do | |||
%{"url" => [%{"href" => href} | _]} = object.data | |||
json(conn, %{url: href}) | |||
end | |||
end | |||
def verify_credentials(%{assigns: %{user: user}} = conn, _) do | |||
chat_token = Phoenix.Token.sign(conn, "user socket", user.id) | |||
@@ -17,8 +17,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do | |||
plug(Pleroma.Plugs.RateLimiter, :search when action in [:search, :search2, :account_search]) | |||
def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do | |||
accounts = User.search(query, search_options(params, user)) | |||
statuses = Activity.search(user, query) | |||
accounts = with_fallback(fn -> User.search(query, search_options(params, user)) end, []) | |||
statuses = with_fallback(fn -> Activity.search(user, query) end, []) | |||
tags_path = Web.base_url() <> "/tag/" | |||
tags = | |||
@@ -40,8 +40,8 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do | |||
end | |||
def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do | |||
accounts = User.search(query, search_options(params, user)) | |||
statuses = Activity.search(user, query) | |||
accounts = with_fallback(fn -> User.search(query, search_options(params, user)) end, []) | |||
statuses = with_fallback(fn -> Activity.search(user, query) end, []) | |||
tags = | |||
query | |||
@@ -76,4 +76,14 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do | |||
for_user: user | |||
] | |||
end | |||
defp with_fallback(f, fallback) do | |||
try do | |||
f.() | |||
rescue | |||
error -> | |||
Logger.error("#{__MODULE__} search error: #{inspect(error)}") | |||
fallback | |||
end | |||
end | |||
end |
@@ -104,7 +104,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do | |||
id: to_string(activity.id), | |||
uri: activity_object.data["id"], | |||
url: activity_object.data["id"], | |||
account: AccountView.render("account.json", %{user: user}), | |||
account: AccountView.render("account.json", %{user: user, for: opts[:for]}), | |||
in_reply_to_id: nil, | |||
in_reply_to_account_id: nil, | |||
reblog: reblogged, | |||
@@ -221,7 +221,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do | |||
id: to_string(activity.id), | |||
uri: object.data["id"], | |||
url: url, | |||
account: AccountView.render("account.json", %{user: user}), | |||
account: AccountView.render("account.json", %{user: user, for: opts[:for]}), | |||
in_reply_to_id: reply_to && to_string(reply_to.id), | |||
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id), | |||
reblog: nil, | |||
@@ -29,9 +29,10 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do | |||
def init(%{qs: qs} = req, state) do | |||
with params <- :cow_qs.parse_qs(qs), | |||
sec_websocket <- :cowboy_req.header("sec-websocket-protocol", req, nil), | |||
access_token <- List.keyfind(params, "access_token", 0), | |||
{_, stream} <- List.keyfind(params, "stream", 0), | |||
{:ok, user} <- allow_request(stream, access_token), | |||
{:ok, user} <- allow_request(stream, [access_token, sec_websocket]), | |||
topic when is_binary(topic) <- expand_topic(stream, params) do | |||
{:cowboy_websocket, req, %{user: user, topic: topic}, %{idle_timeout: @timeout}} | |||
else | |||
@@ -84,13 +85,21 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do | |||
end | |||
# Public streams without authentication. | |||
defp allow_request(stream, nil) when stream in @anonymous_streams do | |||
defp allow_request(stream, [nil, nil]) when stream in @anonymous_streams do | |||
{:ok, nil} | |||
end | |||
# Authenticated streams. | |||
defp allow_request(stream, {"access_token", access_token}) when stream in @streams do | |||
with %Token{user_id: user_id} <- Repo.get_by(Token, token: access_token), | |||
defp allow_request(stream, [access_token, sec_websocket]) when stream in @streams do | |||
token = | |||
with {"access_token", token} <- access_token do | |||
token | |||
else | |||
_ -> sec_websocket | |||
end | |||
with true <- is_bitstring(token), | |||
%Token{user_id: user_id} <- Repo.get_by(Token, token: token), | |||
user = %User{} <- User.get_cached_by_id(user_id) do | |||
{:ok, user} | |||
else | |||
@@ -33,20 +33,7 @@ defmodule Pleroma.Web.MediaProxy do | |||
def encode_url(url) do | |||
secret = Pleroma.Config.get([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. | |||
base64 = | |||
url | |||
|> String.replace("%2F", replacement) | |||
|> URI.decode() | |||
|> URI.encode() | |||
|> String.replace(replacement, "%2F") | |||
|> Base.url_encode64(@base64_opts) | |||
base64 = Base.url_encode64(url, @base64_opts) | |||
sig = :crypto.hmac(:sha, secret, base64) | |||
sig64 = sig |> Base.url_encode64(@base64_opts) | |||
@@ -80,12 +67,4 @@ defmodule Pleroma.Web.MediaProxy do | |||
|> Enum.filter(fn value -> value end) | |||
|> Path.join() | |||
end | |||
defp get_replacement(url, replacement) do | |||
if String.contains?(url, replacement) do | |||
get_replacement(url, replacement <> replacement) | |||
else | |||
replacement | |||
end | |||
end | |||
end |
@@ -121,4 +121,6 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do | |||
acc ++ rendered_tags | |||
end) | |||
end | |||
defp build_attachments(_), do: [] | |||
end |
@@ -117,6 +117,8 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do | |||
end) | |||
end | |||
defp build_attachments(_id, _object), do: [] | |||
defp player_url(id) do | |||
Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice_player, id) | |||
end | |||
@@ -162,7 +162,8 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do | |||
accountActivationRequired: Config.get([:instance, :account_activation_required], false), | |||
invitesEnabled: Config.get([:instance, :invites_enabled], false), | |||
features: features, | |||
restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]) | |||
restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]), | |||
skipThreadContainment: Config.get([:instance, :skip_thread_containment], false) | |||
} | |||
} | |||
end | |||
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.OStatus.NoteHandler do | |||
alias Pleroma.Web.ActivityPub.ActivityPub | |||
alias Pleroma.Web.ActivityPub.Utils | |||
alias Pleroma.Web.CommonAPI | |||
alias Pleroma.Web.Federator | |||
alias Pleroma.Web.OStatus | |||
alias Pleroma.Web.XML | |||
@@ -88,14 +89,15 @@ defmodule Pleroma.Web.OStatus.NoteHandler do | |||
Map.put(note, "external_url", url) | |||
end | |||
def fetch_replied_to_activity(entry, in_reply_to) do | |||
def fetch_replied_to_activity(entry, in_reply_to, options \\ []) do | |||
with %Activity{} = activity <- Activity.get_create_by_object_ap_id(in_reply_to) do | |||
activity | |||
else | |||
_e -> | |||
with in_reply_to_href when not is_nil(in_reply_to_href) <- | |||
with true <- Federator.allowed_incoming_reply_depth?(options[:depth]), | |||
in_reply_to_href when not is_nil(in_reply_to_href) <- | |||
XML.string_from_xpath("//thr:in-reply-to[1]/@href", entry), | |||
{:ok, [activity | _]} <- OStatus.fetch_activity_from_url(in_reply_to_href) do | |||
{:ok, [activity | _]} <- OStatus.fetch_activity_from_url(in_reply_to_href, options) do | |||
activity | |||
else | |||
_e -> nil | |||
@@ -104,7 +106,7 @@ defmodule Pleroma.Web.OStatus.NoteHandler do | |||
end | |||
# TODO: Clean this up a bit. | |||
def handle_note(entry, doc \\ nil) do | |||
def handle_note(entry, doc \\ nil, options \\ []) do | |||
with id <- XML.string_from_xpath("//id", entry), | |||
activity when is_nil(activity) <- Activity.get_create_by_object_ap_id_with_object(id), | |||
[author] <- :xmerl_xpath.string('//author[1]', doc), | |||
@@ -112,7 +114,8 @@ defmodule Pleroma.Web.OStatus.NoteHandler do | |||
content_html <- OStatus.get_content(entry), | |||
cw <- OStatus.get_cw(entry), | |||
in_reply_to <- XML.string_from_xpath("//thr:in-reply-to[1]/@ref", entry), | |||
in_reply_to_activity <- fetch_replied_to_activity(entry, in_reply_to), | |||
options <- Keyword.put(options, :depth, (options[:depth] || 0) + 1), | |||
in_reply_to_activity <- fetch_replied_to_activity(entry, in_reply_to, options), | |||
in_reply_to_object <- | |||
(in_reply_to_activity && Object.normalize(in_reply_to_activity)) || nil, | |||
in_reply_to <- (in_reply_to_object && in_reply_to_object.data["id"]) || in_reply_to, | |||
@@ -54,7 +54,7 @@ defmodule Pleroma.Web.OStatus do | |||
"#{Web.base_url()}/ostatus_subscribe?acct={uri}" | |||
end | |||
def handle_incoming(xml_string) do | |||
def handle_incoming(xml_string, options \\ []) do | |||
with doc when doc != :error <- parse_document(xml_string) do | |||
with {:ok, actor_user} <- find_make_or_update_user(doc), | |||
do: Pleroma.Instances.set_reachable(actor_user.ap_id) | |||
@@ -91,10 +91,12 @@ defmodule Pleroma.Web.OStatus do | |||
_ -> | |||
case object_type do | |||
'http://activitystrea.ms/schema/1.0/note' -> | |||
with {:ok, activity} <- NoteHandler.handle_note(entry, doc), do: activity | |||
with {:ok, activity} <- NoteHandler.handle_note(entry, doc, options), | |||
do: activity | |||
'http://activitystrea.ms/schema/1.0/comment' -> | |||
with {:ok, activity} <- NoteHandler.handle_note(entry, doc), do: activity | |||
with {:ok, activity} <- NoteHandler.handle_note(entry, doc, options), | |||
do: activity | |||
_ -> | |||
Logger.error("Couldn't parse incoming document") | |||
@@ -359,7 +361,7 @@ defmodule Pleroma.Web.OStatus do | |||
end | |||
end | |||
def fetch_activity_from_atom_url(url) do | |||
def fetch_activity_from_atom_url(url, options \\ []) do | |||
with true <- String.starts_with?(url, "http"), | |||
{:ok, %{body: body, status: code}} when code in 200..299 <- | |||
HTTP.get( | |||
@@ -367,7 +369,7 @@ defmodule Pleroma.Web.OStatus do | |||
[{:Accept, "application/atom+xml"}] | |||
) do | |||
Logger.debug("Got document from #{url}, handling...") | |||
handle_incoming(body) | |||
handle_incoming(body, options) | |||
else | |||
e -> | |||
Logger.debug("Couldn't get #{url}: #{inspect(e)}") | |||
@@ -375,13 +377,13 @@ defmodule Pleroma.Web.OStatus do | |||
end | |||
end | |||
def fetch_activity_from_html_url(url) do | |||
def fetch_activity_from_html_url(url, options \\ []) do | |||
Logger.debug("Trying to fetch #{url}") | |||
with true <- String.starts_with?(url, "http"), | |||
{:ok, %{body: body}} <- HTTP.get(url, []), | |||
{:ok, atom_url} <- get_atom_url(body) do | |||
fetch_activity_from_atom_url(atom_url) | |||
fetch_activity_from_atom_url(atom_url, options) | |||
else | |||
e -> | |||
Logger.debug("Couldn't get #{url}: #{inspect(e)}") | |||
@@ -389,11 +391,11 @@ defmodule Pleroma.Web.OStatus do | |||
end | |||
end | |||
def fetch_activity_from_url(url) do | |||
with {:ok, [_ | _] = activities} <- fetch_activity_from_atom_url(url) do | |||
def fetch_activity_from_url(url, options \\ []) do | |||
with {:ok, [_ | _] = activities} <- fetch_activity_from_atom_url(url, options) do | |||
{:ok, activities} | |||
else | |||
_e -> fetch_activity_from_html_url(url) | |||
_e -> fetch_activity_from_html_url(url, options) | |||
end | |||
rescue | |||
e -> | |||
@@ -322,6 +322,10 @@ defmodule Pleroma.Web.Router do | |||
patch("/accounts/update_credentials", MastodonAPIController, :update_credentials) | |||
patch("/accounts/update_avatar", MastodonAPIController, :update_avatar) | |||
patch("/accounts/update_banner", MastodonAPIController, :update_banner) | |||
patch("/accounts/update_background", MastodonAPIController, :update_background) | |||
post("/statuses", MastodonAPIController, :post_status) | |||
delete("/statuses/:id", MastodonAPIController, :delete_status) | |||
@@ -726,6 +730,7 @@ end | |||
defmodule Fallback.RedirectController do | |||
use Pleroma.Web, :controller | |||
require Logger | |||
alias Pleroma.User | |||
alias Pleroma.Web.Metadata | |||
@@ -752,7 +757,20 @@ defmodule Fallback.RedirectController do | |||
def redirector_with_meta(conn, params) do | |||
{:ok, index_content} = File.read(index_file_path()) | |||
tags = Metadata.build_tags(params) | |||
tags = | |||
try do | |||
Metadata.build_tags(params) | |||
rescue | |||
e -> | |||
Logger.error( | |||
"Metadata rendering for #{conn.request_path} failed.\n" <> | |||
Exception.format(:error, e, __STACKTRACE__) | |||
) | |||
"" | |||
end | |||
response = String.replace(index_content, "<!--server-generated-meta-->", tags) | |||
conn | |||
@@ -456,6 +456,16 @@ defmodule Pleroma.Web.TwitterAPI.Controller do | |||
end | |||
end | |||
def update_avatar(%{assigns: %{user: user}} = conn, %{"img" => ""}) do | |||
change = Changeset.change(user, %{avatar: nil}) | |||
{:ok, user} = User.update_and_set_cache(change) | |||
CommonAPI.update(user) | |||
conn | |||
|> put_view(UserView) | |||
|> render("show.json", %{user: user, for: user}) | |||
end | |||
def update_avatar(%{assigns: %{user: user}} = conn, params) do | |||
{:ok, object} = ActivityPub.upload(params, type: :avatar) | |||
change = Changeset.change(user, %{avatar: object.data}) | |||
@@ -467,6 +477,19 @@ defmodule Pleroma.Web.TwitterAPI.Controller do | |||
|> render("show.json", %{user: user, for: user}) | |||
end | |||
def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do | |||
with new_info <- %{"banner" => %{}}, | |||
info_cng <- User.Info.profile_update(user.info, new_info), | |||
changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng), | |||
{:ok, user} <- User.update_and_set_cache(changeset) do | |||
CommonAPI.update(user) | |||
response = %{url: nil} |> Jason.encode!() | |||
conn | |||
|> json_reply(200, response) | |||
end | |||
end | |||
def update_banner(%{assigns: %{user: user}} = conn, params) do | |||
with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner), | |||
new_info <- %{"banner" => object.data}, | |||
@@ -482,6 +505,18 @@ defmodule Pleroma.Web.TwitterAPI.Controller do | |||
end | |||
end | |||
def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do | |||
with new_info <- %{"background" => %{}}, | |||
info_cng <- User.Info.profile_update(user.info, new_info), | |||
changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng), | |||
{:ok, _user} <- User.update_and_set_cache(changeset) do | |||
response = %{url: nil} |> Jason.encode!() | |||
conn | |||
|> json_reply(200, response) | |||
end | |||
end | |||
def update_background(%{assigns: %{user: user}} = conn, params) do | |||
with {:ok, object} <- ActivityPub.upload(params, type: :background), | |||
new_info <- %{"background" => object.data}, | |||
@@ -109,7 +109,6 @@ defmodule Pleroma.Mixfile do | |||
{:phoenix_html, "~> 2.10"}, | |||
{:calendar, "~> 0.17.4"}, | |||
{:cachex, "~> 3.0.2"}, | |||
{:httpoison, "~> 1.2.0"}, | |||
{:poison, "~> 3.0", override: true}, | |||
{:tesla, "~> 1.2"}, | |||
{:jason, "~> 1.0"}, | |||
@@ -154,7 +153,8 @@ defmodule Pleroma.Mixfile do | |||
{:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)}, | |||
{:ex_rated, "~> 1.3"}, | |||
{:plug_static_index_html, "~> 1.0.0"}, | |||
{:excoveralls, "~> 0.11.1", only: :test} | |||
{:excoveralls, "~> 0.11.1", only: :test}, | |||
{:mox, "~> 0.5", only: :test} | |||
] ++ oauth_deps() | |||
end | |||
@@ -177,10 +177,14 @@ defmodule Pleroma.Mixfile do | |||
# Builds a version string made of: | |||
# * the application version | |||
# * a pre-release if ahead of the tag: the describe string (-count-commithash) | |||
# * build info: | |||
# * branch name | |||
# * build metadata: | |||
# * a build name if `PLEROMA_BUILD_NAME` or `:pleroma, :build_name` is defined | |||
# * the mix environment if different than prod | |||
defp version(version) do | |||
identifier_filter = ~r/[^0-9a-z\-]+/i | |||
# Pre-release version, denoted from patch version with a hyphen | |||
{git_tag, git_pre_release} = | |||
with {tag, 0} <- | |||
System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true), | |||
@@ -201,6 +205,19 @@ defmodule Pleroma.Mixfile do | |||
) | |||
end | |||
# Branch name as pre-release version component, denoted with a dot | |||
branch_name = | |||
with {branch_name, 0} <- System.cmd("git", ["rev-parse", "--abbrev-ref", "HEAD"]), | |||
branch_name <- System.get_env("PLEROMA_BUILD_BRANCH") || branch_name, | |||
true <- branch_name != "master" do | |||
branch_name = | |||
branch_name | |||
|> String.trim() | |||
|> String.replace(identifier_filter, "-") | |||
"." <> branch_name | |||
end | |||
build_name = | |||
cond do | |||
name = Application.get_env(:pleroma, :build_name) -> name | |||
@@ -209,28 +226,26 @@ defmodule Pleroma.Mixfile do | |||
end | |||
env_name = if Mix.env() != :prod, do: to_string(Mix.env()) | |||
env_override = System.get_env("PLEROMA_BUILD_ENV") | |||
build = | |||
env_name = | |||
case env_override do | |||
nil -> env_name | |||
env_override when env_override in ["", "prod"] -> nil | |||
env_override -> env_override | |||
end | |||
# Build metadata, denoted with a plus sign | |||
build_metadata = | |||
[build_name, env_name] | |||
|> Enum.filter(fn string -> string && string != "" end) | |||
|> Enum.join("-") | |||
|> Enum.join(".") | |||
|> (fn | |||
"" -> nil | |||
string -> "+" <> string | |||
string -> "+" <> String.replace(string, identifier_filter, "-") | |||
end).() | |||
branch_name = | |||
with {branch_name, 0} <- System.cmd("git", ["rev-parse", "--abbrev-ref", "HEAD"]), | |||
branch_name <- System.get_env("PLEROMA_BUILD_BRANCH") || branch_name, | |||
true <- branch_name != "master" do | |||
branch_name = | |||
String.trim(branch_name) | |||
|> String.replace(~r/[^0-9a-z\-\.]+/i, "-") | |||
"-" <> branch_name | |||
end | |||
[version, git_pre_release, branch_name, build] | |||
[version, git_pre_release, branch_name, build_metadata] | |||
|> Enum.filter(fn string -> string && string != "" end) | |||
|> Enum.join() | |||
end | |||
@@ -57,6 +57,7 @@ | |||
"mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"}, | |||
"mock": {:hex, :mock, "0.3.3", "42a433794b1291a9cf1525c6d26b38e039e0d3a360732b5e467bfc77ef26c914", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, | |||
"mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"}, | |||
"mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm"}, | |||
"nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, | |||
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, | |||
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.3", "6706a148809a29c306062862c803406e88f048277f6e85b68faf73291e820b84", [:mix], [], "hexpm"}, | |||
@@ -71,14 +72,12 @@ | |||
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"}, | |||
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, | |||
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, | |||
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"}, | |||
"postgrex": {:hex, :postgrex, "0.14.3", "5754dee2fdf6e9e508cbf49ab138df964278700b764177e8f3871e658b345a1e", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, | |||
"prometheus": {:hex, :prometheus, "4.2.2", "a830e77b79dc6d28183f4db050a7cac926a6c58f1872f9ef94a35cd989aceef8", [:mix, :rebar3], [], "hexpm"}, | |||
"prometheus_ecto": {:hex, :prometheus_ecto, "1.4.1", "6c768ea9654de871e5b32fab2eac348467b3021604ebebbcbd8bcbe806a65ed5", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"}, | |||
"prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"}, | |||
"prometheus_phoenix": {:hex, :prometheus_phoenix, "1.2.1", "964a74dfbc055f781d3a75631e06ce3816a2913976d1df7830283aa3118a797a", [:mix], [{:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"}, | |||
"prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm"}, | |||
"prometheus_process_collector": {:hex, :prometheus_process_collector, "1.4.3", "657386e8f142fc817347d95c1f3a05ab08710f7df9e7f86db6facaed107ed929", [:rebar3], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"}, | |||
"quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm"}, | |||
"quantum": {:hex, :quantum, "2.3.4", "72a0e8855e2adc101459eac8454787cb74ab4169de6ca50f670e72142d4960e9", [:mix], [{:calendar, "~> 0.17", [hex: :calendar, repo: "hexpm", optional: true]}, {:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}, {:gen_stage, "~> 0.12", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:swarm, "~> 3.3", [hex: :swarm, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: true]}], "hexpm"}, | |||
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"}, | |||
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreatePleroma.User do | |||
use Ecto.Migration | |||
def change do | |||
create table(:users) do | |||
create_if_not_exists table(:users) do | |||
add :email, :string | |||
add :password_hash, :string | |||
add :name, :string | |||
@@ -2,13 +2,13 @@ defmodule Pleroma.Repo.Migrations.CreatePleroma.Activity do | |||
use Ecto.Migration | |||
def change do | |||
create table(:activities) do | |||
create_if_not_exists table(:activities) do | |||
add :data, :map | |||
timestamps() | |||
end | |||
create index(:activities, [:data], using: :gin) | |||
create_if_not_exists index(:activities, [:data], using: :gin) | |||
end | |||
end |
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreatePleroma.Object do | |||
use Ecto.Migration | |||
def change do | |||
create table(:objects) do | |||
create_if_not_exists table(:objects) do | |||
add :data, :map | |||
timestamps() | |||
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.AddIndexToObjects do | |||
use Ecto.Migration | |||
def change do | |||
create index(:objects, [:data], using: :gin) | |||
create_if_not_exists index(:objects, [:data], using: :gin) | |||
end | |||
end |
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.AddUniqueIndexToEmailAndNickname do | |||
use Ecto.Migration | |||
def change do | |||
create unique_index(:users, [:email]) | |||
create unique_index(:users, [:nickname]) | |||
create_if_not_exists unique_index(:users, [:email]) | |||
create_if_not_exists unique_index(:users, [:nickname]) | |||
end | |||
end |
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreateWebsubServerSubscription do | |||
use Ecto.Migration | |||
def change do | |||
create table(:websub_server_subscriptions) do | |||
create_if_not_exists table(:websub_server_subscriptions) do | |||
add :topic, :string | |||
add :callback, :string | |||
add :secret, :string | |||
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreateWebsubClientSubscription do | |||
use Ecto.Migration | |||
def change do | |||
create table(:websub_client_subscriptions) do | |||
create_if_not_exists table(:websub_client_subscriptions) do | |||
add :topic, :string | |||
add :secret, :string | |||
add :valid_until, :naive_datetime_usec | |||
@@ -1,10 +1,12 @@ | |||
defmodule Pleroma.Repo.Migrations.AddIdContraintsToActivitiesAndObjectsPartTwo do | |||
use Ecto.Migration | |||
def change do | |||
def up do | |||
drop_if_exists index(:objects, ["(data->>\"id\")"], name: :objects_unique_apid_index) | |||
drop_if_exists index(:activities, ["(data->>\"id\")"], name: :activities_unique_apid_index) | |||
create unique_index(:objects, ["(data->>'id')"], name: :objects_unique_apid_index) | |||
create unique_index(:activities, ["(data->>'id')"], name: :activities_unique_apid_index) | |||
create_if_not_exists unique_index(:objects, ["(data->>'id')"], name: :objects_unique_apid_index) | |||
create_if_not_exists unique_index(:activities, ["(data->>'id')"], name: :activities_unique_apid_index) | |||
end | |||
def down, do: :ok | |||
end |
@@ -6,6 +6,6 @@ defmodule Pleroma.Repo.Migrations.AddLocalFieldToActivities do | |||
add :local, :boolean, default: true | |||
end | |||
create index(:activities, [:local]) | |||
create_if_not_exists index(:activities, [:local]) | |||
end | |||
end |
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.AddUniqueIndexToAPID do | |||
use Ecto.Migration | |||
def change do | |||
create unique_index(:users, [:ap_id]) | |||
create_if_not_exists unique_index(:users, [:ap_id]) | |||
end | |||
end |
@@ -1,19 +1,31 @@ | |||
defmodule Pleroma.Repo.Migrations.CaseInsensivtivity do | |||
use Ecto.Migration | |||
# Two-steps alters are intentional. | |||
# When alter of 2 columns is done in a single operation, | |||
# inconsistent failures happen because of index on `email` column. | |||
def up do | |||
execute ("create extension if not exists citext") | |||
execute("create extension if not exists citext") | |||
alter table(:users) do | |||
modify(:email, :citext) | |||
end | |||
alter table(:users) do | |||
modify :email, :citext | |||
modify :nickname, :citext | |||
modify(:nickname, :citext) | |||
end | |||
end | |||
def down do | |||
alter table(:users) do | |||
modify :email, :string | |||
modify :nickname, :string | |||
modify(:email, :string) | |||
end | |||
execute ("drop extension if exists citext") | |||
alter table(:users) do | |||
modify(:nickname, :string) | |||
end | |||
execute("drop extension if exists citext") | |||
end | |||
end |
@@ -1,9 +1,16 @@ | |||
defmodule Pleroma.Repo.Migrations.LongerBios do | |||
use Ecto.Migration | |||
def change do | |||
def up do | |||
alter table(:users) do | |||
modify :bio, :text | |||
end | |||
end | |||
def down do | |||
alter table(:users) do | |||
modify :bio, :string | |||
end | |||
end | |||
end |
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.RemoveActivitiesIndex do | |||
use Ecto.Migration | |||
def change do | |||
drop index(:activities, [:data]) | |||
drop_if_exists index(:activities, [:data]) | |||
end | |||
end |
@@ -3,6 +3,6 @@ defmodule Pleroma.Repo.Migrations.AddObjectActivityIndex do | |||
def change do | |||
# This was wrong, now a noop | |||
# create index(:objects, ["(data->'object'->>'id')", "(data->>'type')"], name: :activities_create_objects_index) | |||
# create_if_not_exists index(:objects, ["(data->'object'->>'id')", "(data->>'type')"], name: :activities_create_objects_index) | |||
end | |||
end |
@@ -3,6 +3,6 @@ defmodule Pleroma.Repo.Migrations.AddObjectActivityIndexPartTwo do | |||
def change do | |||
drop_if_exists index(:objects, ["(data->'object'->>'id')", "(data->>'type')"], name: :activities_create_objects_index) | |||
create index(:activities, ["(data->'object'->>'id')", "(data->>'type')"], name: :activities_create_objects_index) | |||
create_if_not_exists index(:activities, ["(data->'object'->>'id')", "(data->>'type')"], name: :activities_create_objects_index) | |||
end | |||
end |
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.AddActorIndexToActivity do | |||
use Ecto.Migration | |||
def change do | |||
create index(:activities, ["(data->>'actor')", "inserted_at desc"], name: :activities_actor_index) | |||
create_if_not_exists index(:activities, ["(data->>'actor')", "inserted_at desc"], name: :activities_actor_index) | |||
end | |||
end |
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.AddMastodonApps do | |||
use Ecto.Migration | |||
def change do | |||
create table(:apps) do | |||
create_if_not_exists table(:apps) do | |||
add :client_name, :string | |||
add :redirect_uris, :string | |||
add :scopes, :string | |||
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreateOAuthAuthorizations do | |||
use Ecto.Migration | |||
def change do | |||
create table(:oauth_authorizations) do | |||
create_if_not_exists table(:oauth_authorizations) do | |||
add :app_id, references(:apps) | |||
add :user_id, references(:users) | |||
add :token, :string | |||
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreateOAuthToken do | |||
use Ecto.Migration | |||
def change do | |||
create table(:oauth_tokens) do | |||
create_if_not_exists table(:oauth_tokens) do | |||
add :app_id, references(:apps) | |||
add :user_id, references(:users) | |||
add :token, :string | |||
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreateNotifications do | |||
use Ecto.Migration | |||
def change do | |||
create table(:notifications) do | |||
create_if_not_exists table(:notifications) do | |||
add :user_id, references(:users, on_delete: :delete_all) | |||
add :activity_id, references(:activities, on_delete: :delete_all) | |||
add :seen, :boolean, default: false | |||
@@ -10,6 +10,6 @@ defmodule Pleroma.Repo.Migrations.CreateNotifications do | |||
timestamps() | |||
end | |||
create index(:notifications, [:user_id]) | |||
create_if_not_exists index(:notifications, [:user_id]) | |||
end | |||
end |
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreatePasswordResetTokens do | |||
use Ecto.Migration | |||
def change do | |||
create table(:password_reset_tokens) do | |||
create_if_not_exists table(:password_reset_tokens) do | |||
add :token, :string | |||
add :user_id, references(:users) | |||
add :used, :boolean, default: false | |||
@@ -12,7 +12,7 @@ defmodule Pleroma.Repo.Migrations.AddActorToActivity do | |||
end | |||
def down do | |||
drop index(:activities, [:actor, "id DESC NULLS LAST"]) | |||
drop_if_exists index(:activities, [:actor, "id DESC NULLS LAST"]) | |||
alter table(:activities) do | |||
remove :actor | |||
end | |||
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.AddLocalIndexToUser do | |||
use Ecto.Migration | |||
def change do | |||
create index(:users, [:local]) | |||
create_if_not_exists index(:users, [:local]) | |||
end | |||
end |
@@ -6,6 +6,6 @@ defmodule Pleroma.Repo.Migrations.AddRecipientsToActivities do | |||
add :recipients, {:array, :string} | |||
end | |||
create index(:activities, [:recipients], using: :gin) | |||
create_if_not_exists index(:activities, [:recipients], using: :gin) | |||
end | |||
end |
@@ -18,4 +18,6 @@ defmodule Pleroma.Repo.Migrations.FillRecipientsInActivities do | |||
end) | |||
end | |||
end | |||
def down, do: :ok | |||
end |
@@ -1,7 +1,7 @@ | |||
defmodule Pleroma.Repo.Migrations.MakeFollowingPostgresArray do | |||
use Ecto.Migration | |||
def change do | |||
def up do | |||
alter table(:users) do | |||
add :following_temp, {:array, :string} | |||
end | |||
@@ -15,4 +15,6 @@ defmodule Pleroma.Repo.Migrations.MakeFollowingPostgresArray do | |||
end | |||
rename table(:users), :following_temp, to: :following | |||
end | |||
def down, do: :ok | |||
end |
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.DropLocalIndexOnActivities do | |||
use Ecto.Migration | |||
def change do | |||
drop index(:users, [:local]) | |||
drop_if_exists index(:users, [:local]) | |||
end | |||
end |
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.ActuallyDropLocalIndex do | |||
use Ecto.Migration | |||
def change do | |||
create index(:users, [:local]) | |||
create_if_not_exists index(:users, [:local]) | |||
drop_if_exists index("activities", :local) | |||
end | |||
end |
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreateLists do | |||
use Ecto.Migration | |||
def change do | |||
create table(:lists) do | |||
create_if_not_exists table(:lists) do | |||
add :user_id, references(:users, on_delete: :delete_all) | |||
add :title, :string | |||
add :following, {:array, :string} | |||
@@ -10,6 +10,6 @@ defmodule Pleroma.Repo.Migrations.CreateLists do | |||
timestamps() | |||
end | |||
create index(:lists, [:user_id]) | |||
create_if_not_exists index(:lists, [:user_id]) | |||
end | |||
end |
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.CreateUserTrigramIndex do | |||
use Ecto.Migration | |||
def change do | |||
create index(:users, ["(nickname || name) gist_trgm_ops"], name: :users_trigram_index, using: :gist) | |||
create_if_not_exists index(:users, ["(nickname || name) gist_trgm_ops"], name: :users_trigram_index, using: :gist) | |||
end | |||
end |
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.AddListFollowIndex do | |||
use Ecto.Migration | |||
def change do | |||
create index(:lists, [:following]) | |||
create_if_not_exists index(:lists, [:following]) | |||
end | |||
end |
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreateUserInviteTokens do | |||
use Ecto.Migration | |||
def change do | |||
create table(:user_invite_tokens) do | |||
create_if_not_exists table(:user_invite_tokens) do | |||
add :token, :string | |||
add :used, :boolean, default: false | |||
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreateFilters do | |||
use Ecto.Migration | |||
def change do | |||
create table(:filters) do | |||
create_if_not_exists table(:filters) do | |||
add :user_id, references(:users, on_delete: :delete_all) | |||
add :filter_id, :integer | |||
add :hide, :boolean | |||
@@ -14,7 +14,7 @@ defmodule Pleroma.Repo.Migrations.CreateFilters do | |||
timestamps() | |||
end | |||
create index(:filters, [:user_id]) | |||
create index(:filters, [:phrase], where: "hide = true", name: :hided_phrases_index) | |||
create_if_not_exists index(:filters, [:user_id]) | |||
create_if_not_exists index(:filters, [:phrase], where: "hide = true", name: :hided_phrases_index) | |||
end | |||
end |
@@ -7,7 +7,7 @@ defmodule Pleroma.Repo.Migrations.AddRecipientsToAndCcFieldsToActivities do | |||
add :recipients_cc, {:array, :string} | |||
end | |||
create index(:activities, [:recipients_to], using: :gin) | |||
create index(:activities, [:recipients_cc], using: :gin) | |||
create_if_not_exists index(:activities, [:recipients_to], using: :gin) | |||
create_if_not_exists index(:activities, [:recipients_cc], using: :gin) | |||
end | |||
end |
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.ActivitiesAddToCcIndices do | |||
use Ecto.Migration | |||
def change do | |||
create index(:activities, ["(data->'to')"], name: :activities_to_index, using: :gin) | |||
create index(:activities, ["(data->'cc')"], name: :activities_cc_index, using: :gin) | |||
create_if_not_exists index(:activities, ["(data->'to')"], name: :activities_to_index, using: :gin) | |||
create_if_not_exists index(:activities, ["(data->'cc')"], name: :activities_cc_index, using: :gin) | |||
end | |||
end |
@@ -1,10 +1,17 @@ | |||
defmodule Pleroma.Repo.Migrations.RemoveRecipientsToAndCcFieldsFromActivities do | |||
use Ecto.Migration | |||
def change do | |||
def up do | |||
alter table(:activities) do | |||
remove :recipients_to | |||
remove :recipients_cc | |||
end | |||
end | |||
def down do | |||
alter table(:activities) do | |||
add :recipients_to, {:array, :string} | |||
add :recipients_cc, {:array, :string} | |||
end | |||
end | |||
end |
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.UsersAddIsModeratorIndex do | |||
use Ecto.Migration | |||
def change do | |||
create index(:users, ["(info->'is_moderator')"], name: :users_is_moderator_index, using: :gin) | |||
create_if_not_exists index(:users, ["(info->'is_moderator')"], name: :users_is_moderator_index, using: :gin) | |||
end | |||
end |
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreatePushSubscriptions do | |||
use Ecto.Migration | |||
def change do | |||
create table("push_subscriptions") do | |||
create_if_not_exists table("push_subscriptions") do | |||
add :user_id, references("users", on_delete: :delete_all) | |||
add :token_id, references("oauth_tokens", on_delete: :delete_all) | |||
add :endpoint, :string | |||
@@ -13,6 +13,6 @@ defmodule Pleroma.Repo.Migrations.CreatePushSubscriptions do | |||
timestamps() | |||
end | |||
create index("push_subscriptions", [:user_id, :token_id], unique: true) | |||
create_if_not_exists index("push_subscriptions", [:user_id, :token_id], unique: true) | |||
end | |||
end |
@@ -1,7 +1,9 @@ | |||
defmodule Pleroma.Repo.Migrations.AddUUIDExtension do | |||
use Ecto.Migration | |||
def change do | |||
def up do | |||
execute("create extension if not exists \"uuid-ossp\"") | |||
end | |||
def down, do: :ok | |||
end |
@@ -1,7 +1,9 @@ | |||
defmodule Pleroma.Repo.Migrations.AddUUIDsToUserInfo do | |||
use Ecto.Migration | |||
def change do | |||
def up do | |||
execute("update users set info = jsonb_set(info, '{\"id\"}', to_jsonb(uuid_generate_v4()))") | |||
end | |||
def down, do: :ok | |||
end |
@@ -6,6 +6,6 @@ defmodule Pleroma.Repo.Migrations.AddTagsToUsers do | |||
add :tags, {:array, :string} | |||
end | |||
create index(:users, [:tags], using: :gin) | |||
create_if_not_exists index(:users, [:tags], using: :gin) | |||
end | |||
end |
@@ -12,7 +12,7 @@ defmodule Pleroma.Repo.Migrations.UsersAndActivitiesFlakeId do | |||
# 4- update relation pkeys with the new ids | |||
# 5- rename the temporary column to id | |||
# 6- re-create the constraints | |||
def change do | |||
def up do | |||
# Old serial int ids are transformed to 128bits with extra padding. | |||
# The application (in `Pleroma.FlakeId`) handles theses IDs properly as integers; to keep compatibility | |||
# with previously issued ids. | |||
@@ -75,6 +75,8 @@ defmodule Pleroma.Repo.Migrations.UsersAndActivitiesFlakeId do | |||
stop_clippy_heartbeats(clippy) | |||
end | |||
def down, do: :ok | |||
defp start_clippy_heartbeats() do | |||
count = from(a in "activities", select: count(a.id)) |> Repo.one! | |||
@@ -37,12 +37,12 @@ defmodule Pleroma.Repo.Migrations.AddVisibilityFunction do | |||
end | |||
def down do | |||
drop( | |||
drop_if_exists( | |||
index(:activities, ["activity_visibility(actor, recipients, data)"], | |||
name: :activities_visibility_index | |||
) | |||
) | |||
execute("drop function activity_visibility(actor varchar, recipients varchar[], data jsonb)") | |||
execute("drop function if exists activity_visibility(actor varchar, recipients varchar[], data jsonb)") | |||
end | |||
end |
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreateUserFtsIndex do | |||
use Ecto.Migration | |||
def change do | |||
create index( | |||
create_if_not_exists index( | |||
:users, | |||
[ | |||
""" | |||
@@ -4,7 +4,7 @@ defmodule Pleroma.Repo.Migrations.FixUserTrigramIndex do | |||
def up do | |||
drop_if_exists(index(:users, [], name: :users_trigram_index)) | |||
create( | |||
create_if_not_exists( | |||
index(:users, ["(trim(nickname || ' ' || coalesce(name, ''))) gist_trgm_ops"], | |||
name: :users_trigram_index, | |||
using: :gist | |||
@@ -15,7 +15,7 @@ defmodule Pleroma.Repo.Migrations.FixUserTrigramIndex do | |||
def down do | |||
drop_if_exists(index(:users, [], name: :users_trigram_index)) | |||
create( | |||
create_if_not_exists( | |||
index(:users, ["(nickname || name) gist_trgm_ops"], name: :users_trigram_index, using: :gist) | |||
) | |||
end | |||
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.UsersAddIsAdminIndex do | |||
use Ecto.Migration | |||
def change do | |||
create(index(:users, ["(info->'is_admin')"], name: :users_is_admin_index, using: :gin)) | |||
create_if_not_exists(index(:users, ["(info->'is_admin')"], name: :users_is_admin_index, using: :gin)) | |||
end | |||
end |
@@ -2,14 +2,14 @@ defmodule Pleroma.Repo.Migrations.CreateInstances do | |||
use Ecto.Migration | |||
def change do | |||
create table(:instances) do | |||
create_if_not_exists table(:instances) do | |||
add :host, :string | |||
add :unreachable_since, :naive_datetime_usec | |||
timestamps() | |||
end | |||
create unique_index(:instances, [:host]) | |||
create index(:instances, [:unreachable_since]) | |||
create_if_not_exists unique_index(:instances, [:host]) | |||
create_if_not_exists index(:instances, [:unreachable_since]) | |||
end | |||
end |
@@ -1,9 +1,11 @@ | |||
defmodule Pleroma.Repo.Migrations.FixInfoIds do | |||
use Ecto.Migration | |||
def change do | |||
def up do | |||
execute( | |||
"update users set info = jsonb_set(info, '{id}', to_jsonb(uuid_generate_v4())) where info->'id' is null;" | |||
) | |||
end | |||
def down, do: :ok | |||
end |
@@ -1,9 +1,15 @@ | |||
defmodule Pleroma.Repo.Migrations.ChangePushSubscriptionsVarchar do | |||
use Ecto.Migration | |||
def change do | |||
def up do | |||
alter table(:push_subscriptions) do | |||
modify(:endpoint, :varchar) | |||
end | |||
end | |||
def down do | |||
alter table(:push_subscriptions) do | |||
modify(:endpoint, :string) | |||
end | |||
end | |||
end |
@@ -19,7 +19,7 @@ defmodule Pleroma.Repo.Migrations.AddCorrectDMIndex do | |||
end | |||
def down do | |||
drop( | |||
drop_if_exists( | |||
index(:activities, ["activity_visibility(actor, recipients, data)", "id DESC"], | |||
name: :activities_visibility_index, | |||
concurrently: true, | |||
@@ -2,11 +2,11 @@ defmodule Pleroma.Repo.Migrations.CreateThreadMutes do | |||
use Ecto.Migration | |||
def change do | |||
create table(:thread_mutes) do | |||
create_if_not_exists 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) | |||
create_if_not_exists unique_index(:thread_mutes, [:user_id, :context], name: :unique_index) | |||
end | |||
end |
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreateRegistrations do | |||
use Ecto.Migration | |||
def change do | |||
create table(:registrations, primary_key: false) do | |||
create_if_not_exists table(:registrations, primary_key: false) do | |||
add :id, :uuid, primary_key: true | |||
add :user_id, references(:users, type: :uuid, on_delete: :delete_all) | |||
add :provider, :string | |||
@@ -12,7 +12,7 @@ defmodule Pleroma.Repo.Migrations.CreateRegistrations do | |||
timestamps() | |||
end | |||
create unique_index(:registrations, [:provider, :uid]) | |||
create unique_index(:registrations, [:user_id, :provider, :uid]) | |||
create_if_not_exists unique_index(:registrations, [:provider, :uid]) | |||
create_if_not_exists unique_index(:registrations, [:user_id, :provider, :uid]) | |||
end | |||
end |
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.CreateNotificationIdIndex do | |||
use Ecto.Migration | |||
def change do | |||
create index(:notifications, ["id desc nulls last"]) | |||
create_if_not_exists index(:notifications, ["id desc nulls last"]) | |||
end | |||
end |
@@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.CreateScheduledActivities do | |||
use Ecto.Migration | |||
def change do | |||
create table(:scheduled_activities) do | |||
create_if_not_exists table(:scheduled_activities) do | |||
add(:user_id, references(:users, type: :uuid, on_delete: :delete_all)) | |||
add(:scheduled_at, :naive_datetime, null: false) | |||
add(:params, :map, null: false) | |||
@@ -10,7 +10,7 @@ defmodule Pleroma.Repo.Migrations.CreateScheduledActivities do | |||
timestamps() | |||
end | |||
create(index(:scheduled_activities, [:scheduled_at])) | |||
create(index(:scheduled_activities, [:user_id])) | |||
create_if_not_exists(index(:scheduled_activities, [:scheduled_at])) | |||
create_if_not_exists(index(:scheduled_activities, [:user_id])) | |||
end | |||
end |
@@ -2,8 +2,8 @@ defmodule Pleroma.Repo.Migrations.AddOauthTokenIndexes do | |||
use Ecto.Migration | |||
def change do | |||
create(unique_index(:oauth_tokens, [:token])) | |||
create(index(:oauth_tokens, [:app_id])) | |||
create(index(:oauth_tokens, [:user_id])) | |||
create_if_not_exists(unique_index(:oauth_tokens, [:token])) | |||
create_if_not_exists(index(:oauth_tokens, [:app_id])) | |||
create_if_not_exists(index(:oauth_tokens, [:user_id])) | |||
end | |||
end |
@@ -1,6 +1,6 @@ | |||
defmodule Pleroma.Repo.Migrations.AddIndexOnSubscribers do | |||
use Ecto.Migration | |||
@disable_ddl_transaction true | |||
def change do | |||
create index(:users, ["(info->'subscribers')"], name: :users_subscribers_index, using: :gin, concurrently: true) | |||
@@ -6,12 +6,12 @@ defmodule Pleroma.Repo.Migrations.CreateConversations do | |||
use Ecto.Migration | |||
def change do | |||
create table(:conversations) do | |||
create_if_not_exists table(:conversations) do | |||
add(:ap_id, :string, null: false) | |||
timestamps() | |||
end | |||
create table(:conversation_participations) do | |||
create_if_not_exists table(:conversation_participations) do | |||
add(:user_id, references(:users, type: :uuid, on_delete: :delete_all)) | |||
add(:conversation_id, references(:conversations, on_delete: :delete_all)) | |||
add(:read, :boolean, default: false) | |||
@@ -19,8 +19,8 @@ defmodule Pleroma.Repo.Migrations.CreateConversations do | |||
timestamps() | |||
end | |||
create index(:conversation_participations, [:conversation_id]) | |||
create unique_index(:conversation_participations, [:user_id, :conversation_id]) | |||
create unique_index(:conversations, [:ap_id]) | |||
create_if_not_exists index(:conversation_participations, [:conversation_id]) | |||
create_if_not_exists unique_index(:conversation_participations, [:user_id, :conversation_id]) | |||
create_if_not_exists unique_index(:conversations, [:ap_id]) | |||
end | |||
end |
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.AddParticipationUpdatedAtIndex do | |||
use Ecto.Migration | |||
def change do | |||
create index(:conversation_participations, ["updated_at desc"]) | |||
create_if_not_exists index(:conversation_participations, ["updated_at desc"]) | |||
end | |||
end |
@@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.AddIndexOnUserInfoDeactivated do | |||
use Ecto.Migration | |||
def change do | |||
create(index(:users, ["(info->'deactivated')"], name: :users_deactivated_index, using: :gin)) | |||
create_if_not_exists(index(:users, ["(info->'deactivated')"], name: :users_deactivated_index, using: :gin)) | |||
end | |||
end |