forked from AkkomaGang/akkoma
Merge remote-tracking branch 'origin/develop' into pleroma-conversations
This commit is contained in:
commit
560dbad538
105 changed files with 1773 additions and 241 deletions
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -23,9 +23,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Not being able to pin unlisted posts
|
- Not being able to pin unlisted posts
|
||||||
- Objects being re-embedded to activities after being updated (e.g faved/reposted). Running 'mix pleroma.database prune_objects' again is advised.
|
- Objects being re-embedded to activities after being updated (e.g faved/reposted). Running 'mix pleroma.database prune_objects' again is advised.
|
||||||
- Metadata rendering errors resulting in the entire page being inaccessible
|
- Metadata rendering errors resulting in the entire page being inaccessible
|
||||||
|
- `federation_incoming_replies_max_depth` option being ignored in certain cases
|
||||||
- Federation/MediaProxy not working with instances that have wrong certificate order
|
- Federation/MediaProxy not working with instances that have wrong certificate order
|
||||||
- Mastodon API: Handling of search timeouts (`/api/v1/search` and `/api/v2/search`)
|
- 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
|
- Mastodon API: Embedded relationships not being properly rendered in the Account entity of Status entity
|
||||||
|
- Mastodon API: follower/following counters not being nullified, when `hide_follows`/`hide_followers` is set
|
||||||
|
- Mastodon API: `muted` in the Status entity, using author's account to determine if the tread was muted
|
||||||
- Mastodon API: Add `account_id`, `type`, `offset`, and `limit` to search API (`/api/v1/search` and `/api/v2/search`)
|
- Mastodon API: Add `account_id`, `type`, `offset`, and `limit` to search API (`/api/v1/search` and `/api/v2/search`)
|
||||||
- Mastodon API, streaming: Fix filtering of notifications based on blocks/mutes/thread mutes
|
- Mastodon API, streaming: Fix filtering of notifications based on blocks/mutes/thread mutes
|
||||||
- ActivityPub C2S: follower/following collection pages being inaccessible even when authentifucated if `hide_followers`/ `hide_follows` was set
|
- ActivityPub C2S: follower/following collection pages being inaccessible even when authentifucated if `hide_followers`/ `hide_follows` was set
|
||||||
|
@ -38,12 +41,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Invalid SemVer version generation, when the current branch does not have commits ahead of tag/checked out on a tag
|
- Invalid SemVer version generation, when the current branch does not have commits ahead of tag/checked out on a tag
|
||||||
- Pleroma.Upload base_url was not automatically whitelisted by MediaProxy. Now your custom CDN or file hosting will be accessed directly as expected.
|
- Pleroma.Upload base_url was not automatically whitelisted by MediaProxy. Now your custom CDN or file hosting will be accessed directly as expected.
|
||||||
- Report email not being sent to admins when the reporter is a remote user
|
- Report email not being sent to admins when the reporter is a remote user
|
||||||
|
- MRF: ensure that subdomain_match calls are case-insensitive
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Conversations: Add Pleroma-specific conversation endpoints and status posting extensions. Run the `bump_all_conversations` task again to create the necessary data.
|
- Conversations: Add Pleroma-specific conversation endpoints and status posting extensions. Run the `bump_all_conversations` task again to create the necessary data.
|
||||||
|
- **Breaking:** MRF describe API, which adds support for exposing configuration information about MRF policies to NodeInfo.
|
||||||
|
Custom modules will need to be updated by adding, at the very least, `def describe, do: {:ok, %{}}` to the MRF policy modules.
|
||||||
- MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`)
|
- MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`)
|
||||||
- MRF: Support for excluding specific domains from Transparency.
|
- MRF: Support for excluding specific domains from Transparency.
|
||||||
- MRF: Support for filtering posts based on who they mention (`Pleroma.Web.ActivityPub.MRF.MentionPolicy`)
|
- MRF: Support for filtering posts based on who they mention (`Pleroma.Web.ActivityPub.MRF.MentionPolicy`)
|
||||||
|
- MRF: Support for filtering posts based on ActivityStreams vocabulary (`Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`)
|
||||||
- MRF (Simple Policy): Support for wildcard domains.
|
- MRF (Simple Policy): Support for wildcard domains.
|
||||||
- Support for wildcard domains in user domain blocks setting.
|
- Support for wildcard domains in user domain blocks setting.
|
||||||
- Configuration: `quarantined_instances` support wildcard domains.
|
- Configuration: `quarantined_instances` support wildcard domains.
|
||||||
|
@ -66,6 +73,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Added synchronization of following/followers counters for external users
|
- Added synchronization of following/followers counters for external users
|
||||||
- Configuration: `enabled` option for `Pleroma.Emails.Mailer`, defaulting to `false`.
|
- Configuration: `enabled` option for `Pleroma.Emails.Mailer`, defaulting to `false`.
|
||||||
- Configuration: Pleroma.Plugs.RateLimiter `bucket_name`, `params` options.
|
- Configuration: Pleroma.Plugs.RateLimiter `bucket_name`, `params` options.
|
||||||
|
- Configuration: `user_bio_length` and `user_name_length` options.
|
||||||
- Addressable lists
|
- Addressable lists
|
||||||
- Twitter API: added rate limit for `/api/account/password_reset` endpoint.
|
- Twitter API: added rate limit for `/api/account/password_reset` endpoint.
|
||||||
- ActivityPub: Add an internal service actor for fetching ActivityPub objects.
|
- ActivityPub: Add an internal service actor for fetching ActivityPub objects.
|
||||||
|
@ -73,6 +81,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Admin API: Endpoint for fetching latest user's statuses
|
- Admin API: Endpoint for fetching latest user's statuses
|
||||||
- Pleroma API: Add `/api/v1/pleroma/accounts/confirmation_resend?email=<email>` for resending account confirmation.
|
- Pleroma API: Add `/api/v1/pleroma/accounts/confirmation_resend?email=<email>` for resending account confirmation.
|
||||||
- Relays: Added a task to list relay subscriptions.
|
- Relays: Added a task to list relay subscriptions.
|
||||||
|
- Mix Tasks: `mix pleroma.database fix_likes_collections`
|
||||||
|
- Federation: Remove `likes` from objects.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
|
- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
|
||||||
|
@ -83,6 +93,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
### Removed
|
### Removed
|
||||||
- Emoji: Remove longfox emojis.
|
- Emoji: Remove longfox emojis.
|
||||||
- Remove `Reply-To` header from report emails for admins.
|
- Remove `Reply-To` header from report emails for admins.
|
||||||
|
- ActivityPub: The `accept_blocks` configuration setting.
|
||||||
|
|
||||||
## [1.0.1] - 2019-07-14
|
## [1.0.1] - 2019-07-14
|
||||||
### Security
|
### Security
|
||||||
|
@ -94,6 +105,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Rich media: Do not crawl private IP ranges
|
- Rich media: Do not crawl private IP ranges
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- Digest email for inactive users
|
||||||
- Add a generic settings store for frontends / clients to use.
|
- Add a generic settings store for frontends / clients to use.
|
||||||
- Explicit addressing option for posting.
|
- Explicit addressing option for posting.
|
||||||
- Optional SSH access mode. (Needs `erlang-ssh` package on some distributions).
|
- Optional SSH access mode. (Needs `erlang-ssh` package on some distributions).
|
||||||
|
@ -120,6 +132,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Configuration: `notify_email` option
|
- Configuration: `notify_email` option
|
||||||
- Configuration: Media proxy `whitelist` option
|
- Configuration: Media proxy `whitelist` option
|
||||||
- Configuration: `report_uri` option
|
- Configuration: `report_uri` option
|
||||||
|
- Configuration: `email_notifications` option
|
||||||
- Configuration: `limit_to_local_content` option
|
- Configuration: `limit_to_local_content` option
|
||||||
- Pleroma API: User subscriptions
|
- Pleroma API: User subscriptions
|
||||||
- Pleroma API: Healthcheck endpoint
|
- Pleroma API: Healthcheck endpoint
|
||||||
|
|
|
@ -253,6 +253,8 @@
|
||||||
skip_thread_containment: true,
|
skip_thread_containment: true,
|
||||||
limit_to_local_content: :unauthenticated,
|
limit_to_local_content: :unauthenticated,
|
||||||
dynamic_configuration: false,
|
dynamic_configuration: false,
|
||||||
|
user_bio_length: 5000,
|
||||||
|
user_name_length: 100,
|
||||||
external_user_synchronization: true
|
external_user_synchronization: true
|
||||||
|
|
||||||
config :pleroma, :markup,
|
config :pleroma, :markup,
|
||||||
|
@ -302,7 +304,6 @@
|
||||||
default_mascot: :pleroma_fox_tan
|
default_mascot: :pleroma_fox_tan
|
||||||
|
|
||||||
config :pleroma, :activitypub,
|
config :pleroma, :activitypub,
|
||||||
accept_blocks: true,
|
|
||||||
unfollow_blocked: true,
|
unfollow_blocked: true,
|
||||||
outgoing_blocks: true,
|
outgoing_blocks: true,
|
||||||
follow_handshake_timeout: 500,
|
follow_handshake_timeout: 500,
|
||||||
|
@ -337,6 +338,10 @@
|
||||||
|
|
||||||
config :pleroma, :mrf_subchain, match_actor: %{}
|
config :pleroma, :mrf_subchain, match_actor: %{}
|
||||||
|
|
||||||
|
config :pleroma, :mrf_vocabulary,
|
||||||
|
accept: [],
|
||||||
|
reject: []
|
||||||
|
|
||||||
config :pleroma, :rich_media,
|
config :pleroma, :rich_media,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
ignore_hosts: [],
|
ignore_hosts: [],
|
||||||
|
@ -514,6 +519,14 @@
|
||||||
total_user_limit: 300,
|
total_user_limit: 300,
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
||||||
|
config :pleroma, :email_notifications,
|
||||||
|
digest: %{
|
||||||
|
active: false,
|
||||||
|
schedule: "0 0 * * 0",
|
||||||
|
interval: 7,
|
||||||
|
inactivity_threshold: 7
|
||||||
|
}
|
||||||
|
|
||||||
config :pleroma, :oauth2,
|
config :pleroma, :oauth2,
|
||||||
token_expires_in: 600,
|
token_expires_in: 600,
|
||||||
issue_new_refresh_token: true,
|
issue_new_refresh_token: true,
|
||||||
|
|
|
@ -81,6 +81,8 @@
|
||||||
config :pleroma, :database, rum_enabled: rum_enabled
|
config :pleroma, :database, rum_enabled: rum_enabled
|
||||||
IO.puts("RUM enabled: #{rum_enabled}")
|
IO.puts("RUM enabled: #{rum_enabled}")
|
||||||
|
|
||||||
|
config :joken, default_signer: "yU8uHKq+yyAkZ11Hx//jcdacWc8yQ1bxAAGrplzB0Zwwjkp35v0RK9SO8WTPr6QZ"
|
||||||
|
|
||||||
config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock
|
config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock
|
||||||
|
|
||||||
try do
|
try do
|
||||||
|
|
|
@ -18,6 +18,7 @@ Note: `strip_exif` has been replaced by `Pleroma.Upload.Filter.Mogrify`.
|
||||||
|
|
||||||
## Pleroma.Uploaders.S3
|
## Pleroma.Uploaders.S3
|
||||||
* `bucket`: S3 bucket name
|
* `bucket`: S3 bucket name
|
||||||
|
* `bucket_namespace`: S3 bucket namespace
|
||||||
* `public_endpoint`: S3 endpoint that the user finally accesses(ex. "https://s3.dualstack.ap-northeast-1.amazonaws.com")
|
* `public_endpoint`: S3 endpoint that the user finally accesses(ex. "https://s3.dualstack.ap-northeast-1.amazonaws.com")
|
||||||
* `truncated_namespace`: If you use S3 compatible service such as Digital Ocean Spaces or CDN, set folder name or "" etc.
|
* `truncated_namespace`: If you use S3 compatible service such as Digital Ocean Spaces or CDN, set folder name or "" etc.
|
||||||
For example, when using CDN to S3 virtual host format, set "".
|
For example, when using CDN to S3 virtual host format, set "".
|
||||||
|
@ -102,6 +103,7 @@ config :pleroma, Pleroma.Emails.Mailer,
|
||||||
* `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links.
|
* `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links.
|
||||||
* `Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`: Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed.
|
* `Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`: Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed.
|
||||||
* `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (see `:mrf_mention` section)
|
* `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (see `:mrf_mention` section)
|
||||||
|
* `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (see `:mrf_vocabulary` section)
|
||||||
* `public`: Makes the client API in authentificated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network.
|
* `public`: Makes the client API in authentificated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network.
|
||||||
* `quarantined_instances`: List of ActivityPub instances where private(DMs, followers-only) activities will not be send.
|
* `quarantined_instances`: List of ActivityPub instances where private(DMs, followers-only) activities will not be send.
|
||||||
* `managed_config`: Whenether the config for pleroma-fe is configured in this config or in ``static/config.json``
|
* `managed_config`: Whenether the config for pleroma-fe is configured in this config or in ``static/config.json``
|
||||||
|
@ -125,6 +127,8 @@ config :pleroma, Pleroma.Emails.Mailer,
|
||||||
* `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). Default: `false`.
|
* `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). Default: `false`.
|
||||||
* `healthcheck`: If set to true, system data will be shown on ``/api/pleroma/healthcheck``.
|
* `healthcheck`: If set to true, system data will be shown on ``/api/pleroma/healthcheck``.
|
||||||
* `remote_post_retention_days`: The default amount of days to retain remote posts when pruning the database.
|
* `remote_post_retention_days`: The default amount of days to retain remote posts when pruning the database.
|
||||||
|
* `user_bio_length`: A user bio maximum length (default: `5000`)
|
||||||
|
* `user_name_length`: A user name maximum length (default: `100`)
|
||||||
* `skip_thread_containment`: Skip filter out broken threads. The default is `false`.
|
* `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`.
|
* `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.
|
* `dynamic_configuration`: Allow transferring configuration to DB with the subsequent customization from Admin api.
|
||||||
|
@ -275,6 +279,10 @@ config :pleroma, :mrf_subchain,
|
||||||
## :mrf_mention
|
## :mrf_mention
|
||||||
* `actors`: A list of actors, for which to drop any posts mentioning.
|
* `actors`: A list of actors, for which to drop any posts mentioning.
|
||||||
|
|
||||||
|
## :mrf_vocabulary
|
||||||
|
* `accept`: A list of ActivityStreams terms to accept. If empty, all supported messages are accepted.
|
||||||
|
* `reject`: A list of ActivityStreams terms to reject. If empty, no messages are rejected.
|
||||||
|
|
||||||
## :media_proxy
|
## :media_proxy
|
||||||
* `enabled`: Enables proxying of remote media to the instance’s proxy
|
* `enabled`: Enables proxying of remote media to the instance’s proxy
|
||||||
* `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.
|
* `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.
|
||||||
|
@ -328,7 +336,6 @@ config :pleroma, Pleroma.Web.Endpoint,
|
||||||
This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls starting with `https://example.com:2020`
|
This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls starting with `https://example.com:2020`
|
||||||
|
|
||||||
## :activitypub
|
## :activitypub
|
||||||
* ``accept_blocks``: Whether to accept incoming block activities from other instances
|
|
||||||
* ``unfollow_blocked``: Whether blocks result in people getting unfollowed
|
* ``unfollow_blocked``: Whether blocks result in people getting unfollowed
|
||||||
* ``outgoing_blocks``: Whether to federate blocks to other instances
|
* ``outgoing_blocks``: Whether to federate blocks to other instances
|
||||||
* ``deny_follow_blocked``: Whether to disallow following an account that has blocked the user in question
|
* ``deny_follow_blocked``: Whether to disallow following an account that has blocked the user in question
|
||||||
|
@ -536,6 +543,18 @@ Authentication / authorization settings.
|
||||||
* `oauth_consumer_template`: OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex`.
|
* `oauth_consumer_template`: OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex`.
|
||||||
* `oauth_consumer_strategies`: the list of enabled OAuth consumer strategies; by default it's set by `OAUTH_CONSUMER_STRATEGIES` environment variable. Each entry in this space-delimited string should be of format `<strategy>` or `<strategy>:<dependency>` (e.g. `twitter` or `keycloak:ueberauth_keycloak_strategy` in case dependency is named differently than `ueberauth_<strategy>`).
|
* `oauth_consumer_strategies`: the list of enabled OAuth consumer strategies; by default it's set by `OAUTH_CONSUMER_STRATEGIES` environment variable. Each entry in this space-delimited string should be of format `<strategy>` or `<strategy>:<dependency>` (e.g. `twitter` or `keycloak:ueberauth_keycloak_strategy` in case dependency is named differently than `ueberauth_<strategy>`).
|
||||||
|
|
||||||
|
## :email_notifications
|
||||||
|
|
||||||
|
Email notifications settings.
|
||||||
|
|
||||||
|
- digest - emails of "what you've missed" for users who have been
|
||||||
|
inactive for a while.
|
||||||
|
- active: globally enable or disable digest emails
|
||||||
|
- schedule: When to send digest email, in [crontab format](https://en.wikipedia.org/wiki/Cron).
|
||||||
|
"0 0 * * 0" is the default, meaning "once a week at midnight on Sunday morning"
|
||||||
|
- interval: Minimum interval between digest emails to one user
|
||||||
|
- inactivity_threshold: Minimum user inactivity threshold
|
||||||
|
|
||||||
## OAuth consumer mode
|
## OAuth consumer mode
|
||||||
|
|
||||||
OAuth consumer mode allows sign in / sign up via external OAuth providers (e.g. Twitter, Facebook, Google, Microsoft, etc.).
|
OAuth consumer mode allows sign in / sign up via external OAuth providers (e.g. Twitter, Facebook, Google, Microsoft, etc.).
|
||||||
|
|
|
@ -36,6 +36,10 @@ defmodule Mix.Tasks.Pleroma.Database do
|
||||||
## Remove duplicated items from following and update followers count for all users
|
## Remove duplicated items from following and update followers count for all users
|
||||||
|
|
||||||
mix pleroma.database update_users_following_followers_counts
|
mix pleroma.database update_users_following_followers_counts
|
||||||
|
|
||||||
|
## Fix the pre-existing "likes" collections for all objects
|
||||||
|
|
||||||
|
mix pleroma.database fix_likes_collections
|
||||||
"""
|
"""
|
||||||
def run(["remove_embedded_objects" | args]) do
|
def run(["remove_embedded_objects" | args]) do
|
||||||
{options, [], []} =
|
{options, [], []} =
|
||||||
|
@ -125,4 +129,36 @@ def run(["prune_objects" | args]) do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def run(["fix_likes_collections"]) do
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
start_pleroma()
|
||||||
|
|
||||||
|
from(object in Object,
|
||||||
|
where: fragment("(?)->>'likes' is not null", object.data),
|
||||||
|
select: %{id: object.id, likes: fragment("(?)->>'likes'", object.data)}
|
||||||
|
)
|
||||||
|
|> Pleroma.RepoStreamer.chunk_stream(100)
|
||||||
|
|> Stream.each(fn objects ->
|
||||||
|
ids =
|
||||||
|
objects
|
||||||
|
|> Enum.filter(fn object -> object.likes |> Jason.decode!() |> is_map() end)
|
||||||
|
|> Enum.map(& &1.id)
|
||||||
|
|
||||||
|
Object
|
||||||
|
|> where([object], object.id in ^ids)
|
||||||
|
|> update([object],
|
||||||
|
set: [
|
||||||
|
data:
|
||||||
|
fragment(
|
||||||
|
"jsonb_set(?, '{likes}', '[]'::jsonb, true)",
|
||||||
|
object.data
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|> Repo.update_all([], timeout: :infinity)
|
||||||
|
end)
|
||||||
|
|> Stream.run()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
33
lib/mix/tasks/pleroma/digest.ex
Normal file
33
lib/mix/tasks/pleroma/digest.ex
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
defmodule Mix.Tasks.Pleroma.Digest do
|
||||||
|
use Mix.Task
|
||||||
|
|
||||||
|
@shortdoc "Manages digest emails"
|
||||||
|
@moduledoc """
|
||||||
|
Manages digest emails
|
||||||
|
|
||||||
|
## Send digest email since given date (user registration date by default)
|
||||||
|
ignoring user activity status.
|
||||||
|
|
||||||
|
``mix pleroma.digest test <nickname> <since_date>``
|
||||||
|
|
||||||
|
Example: ``mix pleroma.digest test donaldtheduck 2019-05-20``
|
||||||
|
"""
|
||||||
|
def run(["test", nickname | opts]) do
|
||||||
|
Mix.Pleroma.start_pleroma()
|
||||||
|
|
||||||
|
user = Pleroma.User.get_by_nickname(nickname)
|
||||||
|
|
||||||
|
last_digest_emailed_at =
|
||||||
|
with [date] <- opts,
|
||||||
|
{:ok, datetime} <- Timex.parse(date, "{YYYY}-{0M}-{0D}") do
|
||||||
|
datetime
|
||||||
|
else
|
||||||
|
_ -> user.inserted_at
|
||||||
|
end
|
||||||
|
|
||||||
|
patched_user = %{user | last_digest_emailed_at: last_digest_emailed_at}
|
||||||
|
|
||||||
|
_user = Pleroma.DigestEmailWorker.perform(patched_user)
|
||||||
|
Mix.shell().info("Digest email have been sent to #{nickname} (#{user.email})")
|
||||||
|
end
|
||||||
|
end
|
|
@ -183,6 +183,7 @@ def run(["gen" | rest]) do
|
||||||
)
|
)
|
||||||
|
|
||||||
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
|
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
|
||||||
|
jwt_secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
|
||||||
signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
|
signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
|
||||||
{web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1)
|
{web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1)
|
||||||
template_dir = Application.app_dir(:pleroma, "priv") <> "/templates"
|
template_dir = Application.app_dir(:pleroma, "priv") <> "/templates"
|
||||||
|
@ -200,6 +201,7 @@ def run(["gen" | rest]) do
|
||||||
dbuser: dbuser,
|
dbuser: dbuser,
|
||||||
dbpass: dbpass,
|
dbpass: dbpass,
|
||||||
secret: secret,
|
secret: secret,
|
||||||
|
jwt_secret: jwt_secret,
|
||||||
signing_salt: signing_salt,
|
signing_salt: signing_salt,
|
||||||
web_push_public_key: Base.url_encode64(web_push_public_key, padding: false),
|
web_push_public_key: Base.url_encode64(web_push_public_key, padding: false),
|
||||||
web_push_private_key: Base.url_encode64(web_push_private_key, padding: false),
|
web_push_private_key: Base.url_encode64(web_push_private_key, padding: false),
|
||||||
|
|
|
@ -224,6 +224,29 @@ def get_create_by_object_ap_id(ap_id) when is_binary(ap_id) do
|
||||||
|
|
||||||
def get_create_by_object_ap_id(_), do: nil
|
def get_create_by_object_ap_id(_), do: nil
|
||||||
|
|
||||||
|
def create_by_object_ap_id_with_object(ap_ids) when is_list(ap_ids) do
|
||||||
|
from(
|
||||||
|
activity in Activity,
|
||||||
|
where:
|
||||||
|
fragment(
|
||||||
|
"coalesce((?)->'object'->>'id', (?)->>'object') = ANY(?)",
|
||||||
|
activity.data,
|
||||||
|
activity.data,
|
||||||
|
^ap_ids
|
||||||
|
),
|
||||||
|
where: fragment("(?)->>'type' = 'Create'", activity.data),
|
||||||
|
inner_join: o in Object,
|
||||||
|
on:
|
||||||
|
fragment(
|
||||||
|
"(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
|
||||||
|
o.data,
|
||||||
|
activity.data,
|
||||||
|
activity.data
|
||||||
|
),
|
||||||
|
preload: [object: o]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
|
def create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
|
||||||
from(
|
from(
|
||||||
activity in Activity,
|
activity in Activity,
|
||||||
|
@ -263,8 +286,8 @@ defp get_in_reply_to_activity_from_object(%Object{data: %{"inReplyTo" => ap_id}}
|
||||||
|
|
||||||
defp get_in_reply_to_activity_from_object(_), do: nil
|
defp get_in_reply_to_activity_from_object(_), do: nil
|
||||||
|
|
||||||
def get_in_reply_to_activity(%Activity{data: %{"object" => object}}) do
|
def get_in_reply_to_activity(%Activity{} = activity) do
|
||||||
get_in_reply_to_activity_from_object(Object.normalize(object))
|
get_in_reply_to_activity_from_object(Object.normalize(activity))
|
||||||
end
|
end
|
||||||
|
|
||||||
def normalize(obj) when is_map(obj), do: get_by_ap_id_with_object(obj["id"])
|
def normalize(obj) when is_map(obj), do: get_by_ap_id_with_object(obj["id"])
|
||||||
|
|
|
@ -162,7 +162,9 @@ def start(_type, _args) do
|
||||||
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
|
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
|
||||||
# for other strategies and supported options
|
# for other strategies and supported options
|
||||||
opts = [strategy: :one_for_one, name: Pleroma.Supervisor]
|
opts = [strategy: :one_for_one, name: Pleroma.Supervisor]
|
||||||
Supervisor.start_link(children, opts)
|
result = Supervisor.start_link(children, opts)
|
||||||
|
:ok = after_supervisor_start()
|
||||||
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
defp setup_instrumenters do
|
defp setup_instrumenters do
|
||||||
|
@ -227,4 +229,17 @@ defp hackney_pool_children do
|
||||||
:hackney_pool.child_spec(pool, options)
|
:hackney_pool.child_spec(pool, options)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp after_supervisor_start do
|
||||||
|
with digest_config <- Application.get_env(:pleroma, :email_notifications)[:digest],
|
||||||
|
true <- digest_config[:active] do
|
||||||
|
PleromaJobQueue.schedule(
|
||||||
|
digest_config[:schedule],
|
||||||
|
:digest_emails,
|
||||||
|
Pleroma.DigestEmailWorker
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
35
lib/pleroma/digest_email_worker.ex
Normal file
35
lib/pleroma/digest_email_worker.ex
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
defmodule Pleroma.DigestEmailWorker do
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
@queue_name :digest_emails
|
||||||
|
|
||||||
|
def perform do
|
||||||
|
config = Pleroma.Config.get([:email_notifications, :digest])
|
||||||
|
negative_interval = -Map.fetch!(config, :interval)
|
||||||
|
inactivity_threshold = Map.fetch!(config, :inactivity_threshold)
|
||||||
|
inactive_users_query = Pleroma.User.list_inactive_users_query(inactivity_threshold)
|
||||||
|
|
||||||
|
now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
|
||||||
|
|
||||||
|
from(u in inactive_users_query,
|
||||||
|
where: fragment(~s(? #> '{"email_notifications","digest"}' @> 'true'), u.info),
|
||||||
|
where: u.last_digest_emailed_at < datetime_add(^now, ^negative_interval, "day"),
|
||||||
|
select: u
|
||||||
|
)
|
||||||
|
|> Pleroma.Repo.all()
|
||||||
|
|> Enum.each(&PleromaJobQueue.enqueue(@queue_name, __MODULE__, [&1]))
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Send digest email to the given user.
|
||||||
|
Updates `last_digest_emailed_at` field for the user and returns the updated user.
|
||||||
|
"""
|
||||||
|
@spec perform(Pleroma.User.t()) :: Pleroma.User.t()
|
||||||
|
def perform(user) do
|
||||||
|
with %Swoosh.Email{} = email <- Pleroma.Emails.UserEmail.digest_email(user) do
|
||||||
|
Pleroma.Emails.Mailer.deliver_async(email)
|
||||||
|
end
|
||||||
|
|
||||||
|
Pleroma.User.touch_last_digest_emailed_at(user)
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,7 +5,7 @@
|
||||||
defmodule Pleroma.Emails.UserEmail do
|
defmodule Pleroma.Emails.UserEmail do
|
||||||
@moduledoc "User emails"
|
@moduledoc "User emails"
|
||||||
|
|
||||||
import Swoosh.Email
|
use Phoenix.Swoosh, view: Pleroma.Web.EmailView, layout: {Pleroma.Web.LayoutView, :email}
|
||||||
|
|
||||||
alias Pleroma.Web.Endpoint
|
alias Pleroma.Web.Endpoint
|
||||||
alias Pleroma.Web.Router
|
alias Pleroma.Web.Router
|
||||||
|
@ -87,4 +87,73 @@ def account_confirmation_email(user) do
|
||||||
|> subject("#{instance_name()} account confirmation")
|
|> subject("#{instance_name()} account confirmation")
|
||||||
|> html_body(html_body)
|
|> html_body(html_body)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Email used in digest email notifications
|
||||||
|
Includes Mentions and New Followers data
|
||||||
|
If there are no mentions (even when new followers exist), the function will return nil
|
||||||
|
"""
|
||||||
|
@spec digest_email(Pleroma.User.t()) :: Swoosh.Email.t() | nil
|
||||||
|
def digest_email(user) do
|
||||||
|
new_notifications =
|
||||||
|
Pleroma.Notification.for_user_since(user, user.last_digest_emailed_at)
|
||||||
|
|> Enum.reduce(%{followers: [], mentions: []}, fn
|
||||||
|
%{activity: %{data: %{"type" => "Create"}, actor: actor} = activity} = notification,
|
||||||
|
acc ->
|
||||||
|
new_mention = %{
|
||||||
|
data: notification,
|
||||||
|
object: Pleroma.Object.normalize(activity),
|
||||||
|
from: Pleroma.User.get_by_ap_id(actor)
|
||||||
|
}
|
||||||
|
|
||||||
|
%{acc | mentions: [new_mention | acc.mentions]}
|
||||||
|
|
||||||
|
%{activity: %{data: %{"type" => "Follow"}, actor: actor} = activity} = notification,
|
||||||
|
acc ->
|
||||||
|
new_follower = %{
|
||||||
|
data: notification,
|
||||||
|
object: Pleroma.Object.normalize(activity),
|
||||||
|
from: Pleroma.User.get_by_ap_id(actor)
|
||||||
|
}
|
||||||
|
|
||||||
|
%{acc | followers: [new_follower | acc.followers]}
|
||||||
|
|
||||||
|
_, acc ->
|
||||||
|
acc
|
||||||
|
end)
|
||||||
|
|
||||||
|
with [_ | _] = mentions <- new_notifications.mentions do
|
||||||
|
html_data = %{
|
||||||
|
instance: instance_name(),
|
||||||
|
user: user,
|
||||||
|
mentions: mentions,
|
||||||
|
followers: new_notifications.followers,
|
||||||
|
unsubscribe_link: unsubscribe_url(user, "digest")
|
||||||
|
}
|
||||||
|
|
||||||
|
new()
|
||||||
|
|> to(recipient(user))
|
||||||
|
|> from(sender())
|
||||||
|
|> subject("Your digest from #{instance_name()}")
|
||||||
|
|> render_body("digest.html", html_data)
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Generate unsubscribe link for given user and notifications type.
|
||||||
|
The link contains JWT token with the data, and subscription can be modified without
|
||||||
|
authorization.
|
||||||
|
"""
|
||||||
|
@spec unsubscribe_url(Pleroma.User.t(), String.t()) :: String.t()
|
||||||
|
def unsubscribe_url(user, notifications_type) do
|
||||||
|
token =
|
||||||
|
%{"sub" => user.id, "act" => %{"unsubscribe" => notifications_type}, "exp" => false}
|
||||||
|
|> Pleroma.JWT.generate_and_sign!()
|
||||||
|
|> Base.encode64()
|
||||||
|
|
||||||
|
Router.Helpers.subscription_url(Pleroma.Web.Endpoint, :unsubscribe, token)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
9
lib/pleroma/jwt.ex
Normal file
9
lib/pleroma/jwt.ex
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
defmodule Pleroma.JWT do
|
||||||
|
use Joken.Config
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def token_config do
|
||||||
|
default_claims(skip: [:aud])
|
||||||
|
|> add_claim("aud", &Pleroma.Web.Endpoint.url/0, &(&1 == Pleroma.Web.Endpoint.url()))
|
||||||
|
end
|
||||||
|
end
|
|
@ -18,6 +18,8 @@ defmodule Pleroma.Notification do
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{}
|
||||||
|
|
||||||
schema "notifications" do
|
schema "notifications" do
|
||||||
field(:seen, :boolean, default: false)
|
field(:seen, :boolean, default: false)
|
||||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||||
|
@ -31,7 +33,7 @@ def changeset(%Notification{} = notification, attrs) do
|
||||||
|> cast(attrs, [:seen])
|
|> cast(attrs, [:seen])
|
||||||
end
|
end
|
||||||
|
|
||||||
def for_user_query(user, opts) do
|
def for_user_query(user, opts \\ []) do
|
||||||
query =
|
query =
|
||||||
Notification
|
Notification
|
||||||
|> where(user_id: ^user.id)
|
|> where(user_id: ^user.id)
|
||||||
|
@ -75,6 +77,25 @@ def for_user(user, opts \\ %{}) do
|
||||||
|> Pagination.fetch_paginated(opts)
|
|> Pagination.fetch_paginated(opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns notifications for user received since given date.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> Pleroma.Notification.for_user_since(%Pleroma.User{}, ~N[2019-04-13 11:22:33])
|
||||||
|
[%Pleroma.Notification{}, %Pleroma.Notification{}]
|
||||||
|
|
||||||
|
iex> Pleroma.Notification.for_user_since(%Pleroma.User{}, ~N[2019-04-15 11:22:33])
|
||||||
|
[]
|
||||||
|
"""
|
||||||
|
@spec for_user_since(Pleroma.User.t(), NaiveDateTime.t()) :: [t()]
|
||||||
|
def for_user_since(user, date) do
|
||||||
|
from(n in for_user_query(user),
|
||||||
|
where: n.updated_at > ^date
|
||||||
|
)
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
|
||||||
def set_read_up_to(%{id: user_id} = _user, id) do
|
def set_read_up_to(%{id: user_id} = _user, id) do
|
||||||
query =
|
query =
|
||||||
from(
|
from(
|
||||||
|
@ -82,7 +103,10 @@ def set_read_up_to(%{id: user_id} = _user, id) do
|
||||||
where: n.user_id == ^user_id,
|
where: n.user_id == ^user_id,
|
||||||
where: n.id <= ^id,
|
where: n.id <= ^id,
|
||||||
update: [
|
update: [
|
||||||
set: [seen: true]
|
set: [
|
||||||
|
seen: true,
|
||||||
|
updated_at: ^NaiveDateTime.utc_now()
|
||||||
|
]
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ def get_file(_) do
|
||||||
|
|
||||||
def put_file(upload) do
|
def put_file(upload) do
|
||||||
{local_path, file} =
|
{local_path, file} =
|
||||||
case Enum.reverse(String.split(upload.path, "/", trim: true)) do
|
case Enum.reverse(Path.split(upload.path)) do
|
||||||
[file] ->
|
[file] ->
|
||||||
{upload_path(), file}
|
{upload_path(), file}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ def put_file(upload) do
|
||||||
|
|
||||||
result_file = Path.join(local_path, file)
|
result_file = Path.join(local_path, file)
|
||||||
|
|
||||||
unless File.exists?(result_file) do
|
if not File.exists?(result_file) do
|
||||||
File.cp!(upload.tempfile, result_file)
|
File.cp!(upload.tempfile, result_file)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Uploaders.MDII do
|
defmodule Pleroma.Uploaders.MDII do
|
||||||
|
@moduledoc "Represents uploader for https://github.com/hakaba-hitoyo/minimal-digital-image-infrastructure"
|
||||||
|
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
alias Pleroma.HTTP
|
alias Pleroma.HTTP
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,12 @@ defmodule Pleroma.Uploaders.S3 do
|
||||||
@behaviour Pleroma.Uploaders.Uploader
|
@behaviour Pleroma.Uploaders.Uploader
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
|
||||||
# The file name is re-encoded with S3's constraints here to comply with previous
|
# The file name is re-encoded with S3's constraints here to comply with previous
|
||||||
# links with less strict filenames
|
# links with less strict filenames
|
||||||
def get_file(file) do
|
def get_file(file) do
|
||||||
config = Pleroma.Config.get([__MODULE__])
|
config = Config.get([__MODULE__])
|
||||||
bucket = Keyword.fetch!(config, :bucket)
|
bucket = Keyword.fetch!(config, :bucket)
|
||||||
|
|
||||||
bucket_with_namespace =
|
bucket_with_namespace =
|
||||||
|
@ -34,15 +36,15 @@ def get_file(file) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def put_file(%Pleroma.Upload{} = upload) do
|
def put_file(%Pleroma.Upload{} = upload) do
|
||||||
config = Pleroma.Config.get([__MODULE__])
|
config = Config.get([__MODULE__])
|
||||||
bucket = Keyword.get(config, :bucket)
|
bucket = Keyword.get(config, :bucket)
|
||||||
|
|
||||||
{:ok, file_data} = File.read(upload.tempfile)
|
|
||||||
|
|
||||||
s3_name = strict_encode(upload.path)
|
s3_name = strict_encode(upload.path)
|
||||||
|
|
||||||
op =
|
op =
|
||||||
ExAws.S3.put_object(bucket, s3_name, file_data, [
|
upload.tempfile
|
||||||
|
|> ExAws.S3.Upload.stream_file()
|
||||||
|
|> ExAws.S3.upload(bucket, s3_name, [
|
||||||
{:acl, :public_read},
|
{:acl, :public_read},
|
||||||
{:content_type, upload.content_type}
|
{:content_type, upload.content_type}
|
||||||
])
|
])
|
||||||
|
|
|
@ -57,6 +57,7 @@ defmodule Pleroma.User do
|
||||||
field(:search_type, :integer, virtual: true)
|
field(:search_type, :integer, virtual: true)
|
||||||
field(:tags, {:array, :string}, default: [])
|
field(:tags, {:array, :string}, default: [])
|
||||||
field(:last_refreshed_at, :naive_datetime_usec)
|
field(:last_refreshed_at, :naive_datetime_usec)
|
||||||
|
field(:last_digest_emailed_at, :naive_datetime)
|
||||||
has_many(:notifications, Notification)
|
has_many(:notifications, Notification)
|
||||||
has_many(:registrations, Registration)
|
has_many(:registrations, Registration)
|
||||||
embeds_one(:info, User.Info)
|
embeds_one(:info, User.Info)
|
||||||
|
@ -151,10 +152,10 @@ def following_count(%User{} = user) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def remote_user_creation(params) do
|
def remote_user_creation(params) do
|
||||||
params =
|
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
||||||
params
|
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
||||||
|> Map.put(:info, params[:info] || %{})
|
|
||||||
|
|
||||||
|
params = Map.put(params, :info, params[:info] || %{})
|
||||||
info_cng = User.Info.remote_user_creation(%User.Info{}, params[:info])
|
info_cng = User.Info.remote_user_creation(%User.Info{}, params[:info])
|
||||||
|
|
||||||
changes =
|
changes =
|
||||||
|
@ -163,8 +164,8 @@ def remote_user_creation(params) do
|
||||||
|> validate_required([:name, :ap_id])
|
|> validate_required([:name, :ap_id])
|
||||||
|> unique_constraint(:nickname)
|
|> unique_constraint(:nickname)
|
||||||
|> validate_format(:nickname, @email_regex)
|
|> validate_format(:nickname, @email_regex)
|
||||||
|> validate_length(:bio, max: 5000)
|
|> validate_length(:bio, max: bio_limit)
|
||||||
|> validate_length(:name, max: 100)
|
|> validate_length(:name, max: name_limit)
|
||||||
|> put_change(:local, false)
|
|> put_change(:local, false)
|
||||||
|> put_embed(:info, info_cng)
|
|> put_embed(:info, info_cng)
|
||||||
|
|
||||||
|
@ -187,22 +188,23 @@ def remote_user_creation(params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_changeset(struct, params \\ %{}) do
|
def update_changeset(struct, params \\ %{}) do
|
||||||
|
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
||||||
|
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
||||||
|
|
||||||
struct
|
struct
|
||||||
|> cast(params, [:bio, :name, :avatar, :following])
|
|> cast(params, [:bio, :name, :avatar, :following])
|
||||||
|> unique_constraint(:nickname)
|
|> unique_constraint(:nickname)
|
||||||
|> validate_format(:nickname, local_nickname_regex())
|
|> validate_format(:nickname, local_nickname_regex())
|
||||||
|> validate_length(:bio, max: 5000)
|
|> validate_length(:bio, max: bio_limit)
|
||||||
|> validate_length(:name, min: 1, max: 100)
|
|> validate_length(:name, min: 1, max: name_limit)
|
||||||
end
|
end
|
||||||
|
|
||||||
def upgrade_changeset(struct, params \\ %{}) do
|
def upgrade_changeset(struct, params \\ %{}) do
|
||||||
params =
|
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
||||||
params
|
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
||||||
|> Map.put(:last_refreshed_at, NaiveDateTime.utc_now())
|
|
||||||
|
|
||||||
info_cng =
|
params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
|
||||||
struct.info
|
info_cng = User.Info.user_upgrade(struct.info, params[:info])
|
||||||
|> User.Info.user_upgrade(params[:info])
|
|
||||||
|
|
||||||
struct
|
struct
|
||||||
|> cast(params, [
|
|> cast(params, [
|
||||||
|
@ -215,8 +217,8 @@ def upgrade_changeset(struct, params \\ %{}) do
|
||||||
])
|
])
|
||||||
|> unique_constraint(:nickname)
|
|> unique_constraint(:nickname)
|
||||||
|> validate_format(:nickname, local_nickname_regex())
|
|> validate_format(:nickname, local_nickname_regex())
|
||||||
|> validate_length(:bio, max: 5000)
|
|> validate_length(:bio, max: bio_limit)
|
||||||
|> validate_length(:name, max: 100)
|
|> validate_length(:name, max: name_limit)
|
||||||
|> put_embed(:info, info_cng)
|
|> put_embed(:info, info_cng)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -243,6 +245,9 @@ def reset_password(%User{id: user_id} = user, data) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def register_changeset(struct, params \\ %{}, opts \\ []) do
|
def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||||
|
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
||||||
|
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
||||||
|
|
||||||
need_confirmation? =
|
need_confirmation? =
|
||||||
if is_nil(opts[:need_confirmation]) do
|
if is_nil(opts[:need_confirmation]) do
|
||||||
Pleroma.Config.get([:instance, :account_activation_required])
|
Pleroma.Config.get([:instance, :account_activation_required])
|
||||||
|
@ -263,8 +268,8 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||||
|> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
|
|> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
|
||||||
|> validate_format(:nickname, local_nickname_regex())
|
|> validate_format(:nickname, local_nickname_regex())
|
||||||
|> validate_format(:email, @email_regex)
|
|> validate_format(:email, @email_regex)
|
||||||
|> validate_length(:bio, max: 1000)
|
|> validate_length(:bio, max: bio_limit)
|
||||||
|> validate_length(:name, min: 1, max: 100)
|
|> validate_length(:name, min: 1, max: name_limit)
|
||||||
|> put_change(:info, info_change)
|
|> put_change(:info, info_change)
|
||||||
|
|
||||||
changeset =
|
changeset =
|
||||||
|
@ -1426,6 +1431,80 @@ def showing_reblogs?(%User{} = user, %User{} = target) do
|
||||||
target.ap_id not in user.info.muted_reblogs
|
target.ap_id not in user.info.muted_reblogs
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
The function returns a query to get users with no activity for given interval of days.
|
||||||
|
Inactive users are those who didn't read any notification, or had any activity where
|
||||||
|
the user is the activity's actor, during `inactivity_threshold` days.
|
||||||
|
Deactivated users will not appear in this list.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> Pleroma.User.list_inactive_users()
|
||||||
|
%Ecto.Query{}
|
||||||
|
"""
|
||||||
|
@spec list_inactive_users_query(integer()) :: Ecto.Query.t()
|
||||||
|
def list_inactive_users_query(inactivity_threshold \\ 7) do
|
||||||
|
negative_inactivity_threshold = -inactivity_threshold
|
||||||
|
now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
|
||||||
|
# Subqueries are not supported in `where` clauses, join gets too complicated.
|
||||||
|
has_read_notifications =
|
||||||
|
from(n in Pleroma.Notification,
|
||||||
|
where: n.seen == true,
|
||||||
|
group_by: n.id,
|
||||||
|
having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
|
||||||
|
select: n.user_id
|
||||||
|
)
|
||||||
|
|> Pleroma.Repo.all()
|
||||||
|
|
||||||
|
from(u in Pleroma.User,
|
||||||
|
left_join: a in Pleroma.Activity,
|
||||||
|
on: u.ap_id == a.actor,
|
||||||
|
where: not is_nil(u.nickname),
|
||||||
|
where: fragment("not (?->'deactivated' @> 'true')", u.info),
|
||||||
|
where: u.id not in ^has_read_notifications,
|
||||||
|
group_by: u.id,
|
||||||
|
having:
|
||||||
|
max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
|
||||||
|
is_nil(max(a.inserted_at))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Enable or disable email notifications for user
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> Pleroma.User.switch_email_notifications(Pleroma.User{info: %{email_notifications: %{"digest" => false}}}, "digest", true)
|
||||||
|
Pleroma.User{info: %{email_notifications: %{"digest" => true}}}
|
||||||
|
|
||||||
|
iex> Pleroma.User.switch_email_notifications(Pleroma.User{info: %{email_notifications: %{"digest" => true}}}, "digest", false)
|
||||||
|
Pleroma.User{info: %{email_notifications: %{"digest" => false}}}
|
||||||
|
"""
|
||||||
|
@spec switch_email_notifications(t(), String.t(), boolean()) ::
|
||||||
|
{:ok, t()} | {:error, Ecto.Changeset.t()}
|
||||||
|
def switch_email_notifications(user, type, status) do
|
||||||
|
info = Pleroma.User.Info.update_email_notifications(user.info, %{type => status})
|
||||||
|
|
||||||
|
change(user)
|
||||||
|
|> put_embed(:info, info)
|
||||||
|
|> update_and_set_cache()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Set `last_digest_emailed_at` value for the user to current time
|
||||||
|
"""
|
||||||
|
@spec touch_last_digest_emailed_at(t()) :: t()
|
||||||
|
def touch_last_digest_emailed_at(user) do
|
||||||
|
now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
|
||||||
|
|
||||||
|
{:ok, updated_user} =
|
||||||
|
user
|
||||||
|
|> change(%{last_digest_emailed_at: now})
|
||||||
|
|> update_and_set_cache()
|
||||||
|
|
||||||
|
updated_user
|
||||||
|
end
|
||||||
|
|
||||||
@spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
|
@spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
|
||||||
def toggle_confirmation(%User{} = user) do
|
def toggle_confirmation(%User{} = user) do
|
||||||
need_confirmation? = !user.info.confirmation_pending
|
need_confirmation? = !user.info.confirmation_pending
|
||||||
|
|
|
@ -45,6 +45,7 @@ defmodule Pleroma.User.Info do
|
||||||
field(:hide_follows, :boolean, default: false)
|
field(:hide_follows, :boolean, default: false)
|
||||||
field(:hide_favorites, :boolean, default: true)
|
field(:hide_favorites, :boolean, default: true)
|
||||||
field(:pinned_activities, {:array, :string}, default: [])
|
field(:pinned_activities, {:array, :string}, default: [])
|
||||||
|
field(:email_notifications, :map, default: %{"digest" => false})
|
||||||
field(:mascot, :map, default: nil)
|
field(:mascot, :map, default: nil)
|
||||||
field(:emoji, {:array, :map}, default: [])
|
field(:emoji, {:array, :map}, default: [])
|
||||||
field(:pleroma_settings_store, :map, default: %{})
|
field(:pleroma_settings_store, :map, default: %{})
|
||||||
|
@ -95,6 +96,30 @@ def update_notification_settings(info, settings) do
|
||||||
|> validate_required([:notification_settings])
|
|> validate_required([:notification_settings])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Update email notifications in the given User.Info struct.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
iex> update_email_notifications(%Pleroma.User.Info{email_notifications: %{"digest" => false}}, %{"digest" => true})
|
||||||
|
%Pleroma.User.Info{email_notifications: %{"digest" => true}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
@spec update_email_notifications(t(), map()) :: Ecto.Changeset.t()
|
||||||
|
def update_email_notifications(info, settings) do
|
||||||
|
email_notifications =
|
||||||
|
info.email_notifications
|
||||||
|
|> Map.merge(settings)
|
||||||
|
|> Map.take(["digest"])
|
||||||
|
|
||||||
|
params = %{email_notifications: email_notifications}
|
||||||
|
fields = [:email_notifications]
|
||||||
|
|
||||||
|
info
|
||||||
|
|> cast(params, fields)
|
||||||
|
|> validate_required(fields)
|
||||||
|
end
|
||||||
|
|
||||||
def add_to_note_count(info, number) do
|
def add_to_note_count(info, number) do
|
||||||
set_note_count(info, info.note_count + number)
|
set_note_count(info, info.note_count + number)
|
||||||
end
|
end
|
||||||
|
|
|
@ -518,6 +518,8 @@ defp fetch_activities_for_context_query(context, opts) do
|
||||||
|
|
||||||
from(activity in Activity)
|
from(activity in Activity)
|
||||||
|> maybe_preload_objects(opts)
|
|> maybe_preload_objects(opts)
|
||||||
|
|> maybe_preload_bookmarks(opts)
|
||||||
|
|> maybe_set_thread_muted_field(opts)
|
||||||
|> restrict_blocked(opts)
|
|> restrict_blocked(opts)
|
||||||
|> restrict_recipients(recipients, opts["user"])
|
|> restrict_recipients(recipients, opts["user"])
|
||||||
|> where(
|
|> where(
|
||||||
|
@ -531,6 +533,7 @@ defp fetch_activities_for_context_query(context, opts) do
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|> exclude_poll_votes(opts)
|
|> exclude_poll_votes(opts)
|
||||||
|
|> exclude_id(opts)
|
||||||
|> order_by([activity], desc: activity.id)
|
|> order_by([activity], desc: activity.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -623,6 +626,7 @@ def fetch_user_activities(user, reading_user, params \\ %{}) do
|
||||||
params =
|
params =
|
||||||
params
|
params
|
||||||
|> Map.put("type", ["Create", "Announce"])
|
|> Map.put("type", ["Create", "Announce"])
|
||||||
|
|> Map.put("user", reading_user)
|
||||||
|> Map.put("actor_id", user.ap_id)
|
|> Map.put("actor_id", user.ap_id)
|
||||||
|> Map.put("whole_db", true)
|
|> Map.put("whole_db", true)
|
||||||
|> Map.put("pinned_activity_ids", user.info.pinned_activities)
|
|> Map.put("pinned_activity_ids", user.info.pinned_activities)
|
||||||
|
@ -870,6 +874,12 @@ defp exclude_poll_votes(query, _) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp exclude_id(query, %{"exclude_id" => id}) when is_binary(id) do
|
||||||
|
from(activity in query, where: activity.id != ^id)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp exclude_id(query, _), do: query
|
||||||
|
|
||||||
defp maybe_preload_objects(query, %{"skip_preload" => true}), do: query
|
defp maybe_preload_objects(query, %{"skip_preload" => true}), do: query
|
||||||
|
|
||||||
defp maybe_preload_objects(query, _) do
|
defp maybe_preload_objects(query, _) do
|
||||||
|
|
|
@ -28,11 +28,43 @@ defp get_policies(_), do: []
|
||||||
|
|
||||||
@spec subdomains_regex([String.t()]) :: [Regex.t()]
|
@spec subdomains_regex([String.t()]) :: [Regex.t()]
|
||||||
def subdomains_regex(domains) when is_list(domains) do
|
def subdomains_regex(domains) when is_list(domains) do
|
||||||
for domain <- domains, do: ~r(^#{String.replace(domain, "*.", "(.*\\.)*")}$)
|
for domain <- domains, do: ~r(^#{String.replace(domain, "*.", "(.*\\.)*")}$)i
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec subdomain_match?([Regex.t()], String.t()) :: boolean()
|
@spec subdomain_match?([Regex.t()], String.t()) :: boolean()
|
||||||
def subdomain_match?(domains, host) do
|
def subdomain_match?(domains, host) do
|
||||||
Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
|
Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@callback describe() :: {:ok | :error, Map.t()}
|
||||||
|
|
||||||
|
def describe(policies) do
|
||||||
|
{:ok, policy_configs} =
|
||||||
|
policies
|
||||||
|
|> Enum.reduce({:ok, %{}}, fn
|
||||||
|
policy, {:ok, data} ->
|
||||||
|
{:ok, policy_data} = policy.describe()
|
||||||
|
{:ok, Map.merge(data, policy_data)}
|
||||||
|
|
||||||
|
_, error ->
|
||||||
|
error
|
||||||
|
end)
|
||||||
|
|
||||||
|
mrf_policies =
|
||||||
|
get_policies()
|
||||||
|
|> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
|
||||||
|
|
||||||
|
exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
|
||||||
|
|
||||||
|
base =
|
||||||
|
%{
|
||||||
|
mrf_policies: mrf_policies,
|
||||||
|
exclusions: length(exclusions) > 0
|
||||||
|
}
|
||||||
|
|> Map.merge(policy_configs)
|
||||||
|
|
||||||
|
{:ok, base}
|
||||||
|
end
|
||||||
|
|
||||||
|
def describe, do: get_policies() |> describe()
|
||||||
end
|
end
|
||||||
|
|
|
@ -62,4 +62,7 @@ def filter(%{"type" => "Follow", "actor" => actor_id} = message) do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(message), do: {:ok, message}
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
|
defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
|
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
# has the user successfully posted before?
|
# has the user successfully posted before?
|
||||||
|
@ -22,6 +24,7 @@ defp contains_links?(%{"content" => content} = _object) do
|
||||||
|
|
||||||
defp contains_links?(_), do: false
|
defp contains_links?(_), do: false
|
||||||
|
|
||||||
|
@impl true
|
||||||
def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do
|
def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do
|
||||||
with {:ok, %User{} = u} <- User.get_or_fetch_by_ap_id(actor),
|
with {:ok, %User{} = u} <- User.get_or_fetch_by_ap_id(actor),
|
||||||
{:contains_links, true} <- {:contains_links, contains_links?(object)},
|
{:contains_links, true} <- {:contains_links, contains_links?(object)},
|
||||||
|
@ -45,4 +48,7 @@ def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message
|
||||||
|
|
||||||
# in all other cases, pass through
|
# in all other cases, pass through
|
||||||
def filter(message), do: {:ok, message}
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,4 +12,7 @@ def filter(object) do
|
||||||
Logger.info("REJECTING #{inspect(object)}")
|
Logger.info("REJECTING #{inspect(object)}")
|
||||||
{:reject, object}
|
{:reject, object}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -39,4 +39,6 @@ def filter(%{"type" => "Create", "object" => child_object} = object) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter(object), do: {:ok, object}
|
def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -90,4 +90,7 @@ def filter(%{"type" => "Create"} = message) do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(message), do: {:ok, message}
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{mrf_hellthread: Pleroma.Config.get([:mrf_hellthread])}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -96,4 +96,36 @@ def filter(%{"type" => "Create", "object" => %{"content" => _content}} = message
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(message), do: {:ok, message}
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe do
|
||||||
|
# This horror is needed to convert regex sigils to strings
|
||||||
|
mrf_keyword =
|
||||||
|
Pleroma.Config.get(:mrf_keyword, [])
|
||||||
|
|> Enum.map(fn {key, value} ->
|
||||||
|
{key,
|
||||||
|
Enum.map(value, fn
|
||||||
|
{pattern, replacement} ->
|
||||||
|
%{
|
||||||
|
"pattern" =>
|
||||||
|
if not is_binary(pattern) do
|
||||||
|
inspect(pattern)
|
||||||
|
else
|
||||||
|
pattern
|
||||||
|
end,
|
||||||
|
"replacement" => replacement
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern ->
|
||||||
|
if not is_binary(pattern) do
|
||||||
|
inspect(pattern)
|
||||||
|
else
|
||||||
|
pattern
|
||||||
|
end
|
||||||
|
end)}
|
||||||
|
end)
|
||||||
|
|> Enum.into(%{})
|
||||||
|
|
||||||
|
{:ok, %{mrf_keyword: mrf_keyword}}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -53,4 +53,7 @@ def filter(
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(message), do: {:ok, message}
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,4 +21,7 @@ def filter(%{"type" => "Create"} = message) do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(message), do: {:ok, message}
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,4 +19,7 @@ def filter(
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(object), do: {:ok, object}
|
def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,4 +10,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do
|
||||||
def filter(object) do
|
def filter(object) do
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,4 +21,6 @@ def filter(%{"type" => "Create", "object" => child_object} = object) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter(object), do: {:ok, object}
|
def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -44,4 +44,7 @@ def filter(%{"type" => "Create"} = object) do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(object), do: {:ok, object}
|
def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{mrf_rejectnonpublic: Pleroma.Config.get([:mrf_rejectnonpublic])}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -177,4 +177,16 @@ def filter(%{"id" => actor, "type" => obj_type} = object)
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter(object), do: {:ok, object}
|
def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe do
|
||||||
|
exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
|
||||||
|
|
||||||
|
mrf_simple =
|
||||||
|
Pleroma.Config.get(:mrf_simple)
|
||||||
|
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
|
||||||
|
|> Enum.into(%{})
|
||||||
|
|
||||||
|
{:ok, %{mrf_simple: mrf_simple}}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -37,4 +37,7 @@ def filter(%{"actor" => actor} = message) do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(message), do: {:ok, message}
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -165,4 +165,7 @@ def filter(%{"actor" => actor, "type" => "Create"} = message),
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(message), do: {:ok, message}
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -32,4 +32,13 @@ def filter(%{"actor" => actor} = object) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter(object), do: {:ok, object}
|
def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe do
|
||||||
|
mrf_user_allowlist =
|
||||||
|
Config.get([:mrf_user_allowlist], [])
|
||||||
|
|> Enum.into(%{}, fn {k, v} -> {k, length(v)} end)
|
||||||
|
|
||||||
|
{:ok, %{mrf_user_allowlist: mrf_user_allowlist}}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
36
lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex
Normal file
36
lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
|
||||||
|
@moduledoc "Filter messages which belong to certain activity vocabularies"
|
||||||
|
|
||||||
|
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||||
|
|
||||||
|
def filter(%{"type" => "Undo", "object" => child_message} = message) do
|
||||||
|
with {:ok, _} <- filter(child_message) do
|
||||||
|
{:ok, message}
|
||||||
|
else
|
||||||
|
{:reject, nil} ->
|
||||||
|
{:reject, nil}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter(%{"type" => message_type} = message) do
|
||||||
|
with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]),
|
||||||
|
rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]),
|
||||||
|
true <-
|
||||||
|
length(accepted_vocabulary) == 0 || Enum.member?(accepted_vocabulary, message_type),
|
||||||
|
false <-
|
||||||
|
length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type),
|
||||||
|
{:ok, _} <- filter(message["object"]) do
|
||||||
|
{:ok, message}
|
||||||
|
else
|
||||||
|
_ -> {:reject, nil}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
def describe, do: {:ok, %{mrf_vocabulary: Pleroma.Config.get(:mrf_vocabulary)}}
|
||||||
|
end
|
|
@ -14,6 +14,7 @@ def get_actor do
|
||||||
|> User.get_or_create_service_actor_by_ap_id()
|
|> User.get_or_create_service_actor_by_ap_id()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec follow(String.t()) :: {:ok, Activity.t()} | {:error, any()}
|
||||||
def follow(target_instance) do
|
def follow(target_instance) do
|
||||||
with %User{} = local_user <- get_actor(),
|
with %User{} = local_user <- get_actor(),
|
||||||
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
|
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
|
||||||
|
@ -21,12 +22,17 @@ def follow(target_instance) do
|
||||||
Logger.info("relay: followed instance: #{target_instance}; id=#{activity.data["id"]}")
|
Logger.info("relay: followed instance: #{target_instance}; id=#{activity.data["id"]}")
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
|
{:error, _} = error ->
|
||||||
|
Logger.error("error: #{inspect(error)}")
|
||||||
|
error
|
||||||
|
|
||||||
e ->
|
e ->
|
||||||
Logger.error("error: #{inspect(e)}")
|
Logger.error("error: #{inspect(e)}")
|
||||||
{:error, e}
|
{:error, e}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec unfollow(String.t()) :: {:ok, Activity.t()} | {:error, any()}
|
||||||
def unfollow(target_instance) do
|
def unfollow(target_instance) do
|
||||||
with %User{} = local_user <- get_actor(),
|
with %User{} = local_user <- get_actor(),
|
||||||
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
|
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
|
||||||
|
@ -34,20 +40,27 @@ def unfollow(target_instance) do
|
||||||
Logger.info("relay: unfollowed instance: #{target_instance}: id=#{activity.data["id"]}")
|
Logger.info("relay: unfollowed instance: #{target_instance}: id=#{activity.data["id"]}")
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
|
{:error, _} = error ->
|
||||||
|
Logger.error("error: #{inspect(error)}")
|
||||||
|
error
|
||||||
|
|
||||||
e ->
|
e ->
|
||||||
Logger.error("error: #{inspect(e)}")
|
Logger.error("error: #{inspect(e)}")
|
||||||
{:error, e}
|
{:error, e}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec publish(any()) :: {:ok, Activity.t(), Object.t()} | {:error, any()}
|
||||||
def publish(%Activity{data: %{"type" => "Create"}} = activity) do
|
def publish(%Activity{data: %{"type" => "Create"}} = activity) do
|
||||||
with %User{} = user <- get_actor(),
|
with %User{} = user <- get_actor(),
|
||||||
%Object{} = object <- Object.normalize(activity) do
|
%Object{} = object <- Object.normalize(activity) do
|
||||||
ActivityPub.announce(user, object, nil, true, false)
|
ActivityPub.announce(user, object, nil, true, false)
|
||||||
else
|
else
|
||||||
e -> Logger.error("error: #{inspect(e)}")
|
e ->
|
||||||
|
Logger.error("error: #{inspect(e)}")
|
||||||
|
{:error, inspect(e)}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def publish(_), do: nil
|
def publish(_), do: {:error, "Not implemented"}
|
||||||
end
|
end
|
||||||
|
|
|
@ -26,6 +26,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||||
"""
|
"""
|
||||||
def fix_object(object, options \\ []) do
|
def fix_object(object, options \\ []) do
|
||||||
object
|
object
|
||||||
|
|> strip_internal_fields
|
||||||
|> fix_actor
|
|> fix_actor
|
||||||
|> fix_url
|
|> fix_url
|
||||||
|> fix_attachments
|
|> fix_attachments
|
||||||
|
@ -34,7 +35,6 @@ def fix_object(object, options \\ []) do
|
||||||
|> fix_emoji
|
|> fix_emoji
|
||||||
|> fix_tag
|
|> fix_tag
|
||||||
|> fix_content_map
|
|> fix_content_map
|
||||||
|> fix_likes
|
|
||||||
|> fix_addressing
|
|> fix_addressing
|
||||||
|> fix_summary
|
|> fix_summary
|
||||||
|> fix_type(options)
|
|> fix_type(options)
|
||||||
|
@ -151,20 +151,6 @@ def fix_actor(%{"attributedTo" => actor} = object) do
|
||||||
|> Map.put("actor", Containment.get_actor(%{"actor" => actor}))
|
|> Map.put("actor", Containment.get_actor(%{"actor" => actor}))
|
||||||
end
|
end
|
||||||
|
|
||||||
# Check for standardisation
|
|
||||||
# This is what Peertube does
|
|
||||||
# curl -H 'Accept: application/activity+json' $likes | jq .totalItems
|
|
||||||
# Prismo returns only an integer (count) as "likes"
|
|
||||||
def fix_likes(%{"likes" => likes} = object) when not is_map(likes) do
|
|
||||||
object
|
|
||||||
|> Map.put("likes", [])
|
|
||||||
|> Map.put("like_count", 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
def fix_likes(object) do
|
|
||||||
object
|
|
||||||
end
|
|
||||||
|
|
||||||
def fix_in_reply_to(object, options \\ [])
|
def fix_in_reply_to(object, options \\ [])
|
||||||
|
|
||||||
def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
|
def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
|
||||||
|
@ -347,13 +333,15 @@ def fix_content_map(object), do: object
|
||||||
|
|
||||||
def fix_type(object, options \\ [])
|
def fix_type(object, options \\ [])
|
||||||
|
|
||||||
def fix_type(%{"inReplyTo" => reply_id} = object, options) when is_binary(reply_id) do
|
def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options)
|
||||||
|
when is_binary(reply_id) do
|
||||||
reply =
|
reply =
|
||||||
if Federator.allowed_incoming_reply_depth?(options[:depth]) do
|
with true <- Federator.allowed_incoming_reply_depth?(options[:depth]),
|
||||||
Object.normalize(reply_id, true)
|
{:ok, object} <- get_obj_helper(reply_id, options) do
|
||||||
|
object
|
||||||
end
|
end
|
||||||
|
|
||||||
if reply && (reply.data["type"] == "Question" and object["name"]) do
|
if reply && reply.data["type"] == "Question" do
|
||||||
Map.put(object, "type", "Answer")
|
Map.put(object, "type", "Answer")
|
||||||
else
|
else
|
||||||
object
|
object
|
||||||
|
@ -713,8 +701,7 @@ def handle_incoming(
|
||||||
} = _data,
|
} = _data,
|
||||||
_options
|
_options
|
||||||
) do
|
) do
|
||||||
with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
|
with %User{local: true} = blocked <- User.get_cached_by_ap_id(blocked),
|
||||||
%User{local: true} = blocked <- User.get_cached_by_ap_id(blocked),
|
|
||||||
{:ok, %User{} = blocker} <- User.get_or_fetch_by_ap_id(blocker),
|
{:ok, %User{} = blocker} <- User.get_or_fetch_by_ap_id(blocker),
|
||||||
{:ok, activity} <- ActivityPub.unblock(blocker, blocked, id, false) do
|
{:ok, activity} <- ActivityPub.unblock(blocker, blocked, id, false) do
|
||||||
User.unblock(blocker, blocked)
|
User.unblock(blocker, blocked)
|
||||||
|
@ -728,8 +715,7 @@ def handle_incoming(
|
||||||
%{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data,
|
%{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data,
|
||||||
_options
|
_options
|
||||||
) do
|
) do
|
||||||
with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
|
with %User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
|
||||||
%User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
|
|
||||||
{:ok, %User{} = blocker} = User.get_or_fetch_by_ap_id(blocker),
|
{:ok, %User{} = blocker} = User.get_or_fetch_by_ap_id(blocker),
|
||||||
{:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do
|
{:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do
|
||||||
User.unfollow(blocker, blocked)
|
User.unfollow(blocker, blocked)
|
||||||
|
@ -784,7 +770,6 @@ def prepare_object(object) do
|
||||||
|> add_mention_tags
|
|> add_mention_tags
|
||||||
|> add_emoji_tags
|
|> add_emoji_tags
|
||||||
|> add_attributed_to
|
|> add_attributed_to
|
||||||
|> add_likes
|
|
||||||
|> prepare_attachments
|
|> prepare_attachments
|
||||||
|> set_conversation
|
|> set_conversation
|
||||||
|> set_reply_to_uri
|
|> set_reply_to_uri
|
||||||
|
@ -971,22 +956,6 @@ def add_attributed_to(object) do
|
||||||
|> Map.put("attributedTo", attributed_to)
|
|> Map.put("attributedTo", attributed_to)
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_likes(%{"id" => id, "like_count" => likes} = object) do
|
|
||||||
likes = %{
|
|
||||||
"id" => "#{id}/likes",
|
|
||||||
"first" => "#{id}/likes?page=1",
|
|
||||||
"type" => "OrderedCollection",
|
|
||||||
"totalItems" => likes
|
|
||||||
}
|
|
||||||
|
|
||||||
object
|
|
||||||
|> Map.put("likes", likes)
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_likes(object) do
|
|
||||||
object
|
|
||||||
end
|
|
||||||
|
|
||||||
def prepare_attachments(object) do
|
def prepare_attachments(object) do
|
||||||
attachments =
|
attachments =
|
||||||
(object["attachment"] || [])
|
(object["attachment"] || [])
|
||||||
|
@ -1002,6 +971,7 @@ def prepare_attachments(object) do
|
||||||
defp strip_internal_fields(object) do
|
defp strip_internal_fields(object) do
|
||||||
object
|
object
|
||||||
|> Map.drop([
|
|> Map.drop([
|
||||||
|
"likes",
|
||||||
"like_count",
|
"like_count",
|
||||||
"announcements",
|
"announcements",
|
||||||
"announcement_count",
|
"announcement_count",
|
||||||
|
|
20
lib/pleroma/web/mailer/subscription_controller.ex
Normal file
20
lib/pleroma/web/mailer/subscription_controller.ex
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
defmodule Pleroma.Web.Mailer.SubscriptionController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Pleroma.JWT
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
def unsubscribe(conn, %{"token" => encoded_token}) do
|
||||||
|
with {:ok, token} <- Base.decode64(encoded_token),
|
||||||
|
{:ok, claims} <- JWT.verify_and_validate(token),
|
||||||
|
%{"act" => %{"unsubscribe" => type}, "sub" => uid} <- claims,
|
||||||
|
%User{} = user <- Repo.get(User, uid),
|
||||||
|
{:ok, _user} <- User.switch_email_notifications(user, type, false) do
|
||||||
|
render(conn, "unsubscribe_success.html", email: user.email)
|
||||||
|
else
|
||||||
|
_err ->
|
||||||
|
render(conn, "unsubscribe_failure.html")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -13,10 +13,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
@spec follow(User.t(), User.t(), map) :: {:ok, User.t()} | {:error, String.t()}
|
||||||
def follow(follower, followed, params \\ %{}) do
|
def follow(follower, followed, params \\ %{}) do
|
||||||
options = cast_params(params)
|
|
||||||
reblogs = options[:reblogs]
|
|
||||||
|
|
||||||
result =
|
result =
|
||||||
if not User.following?(follower, followed) do
|
if not User.following?(follower, followed) do
|
||||||
CommonAPI.follow(follower, followed)
|
CommonAPI.follow(follower, followed)
|
||||||
|
@ -24,19 +22,25 @@ def follow(follower, followed, params \\ %{}) do
|
||||||
{:ok, follower, followed, nil}
|
{:ok, follower, followed, nil}
|
||||||
end
|
end
|
||||||
|
|
||||||
with {:ok, follower, followed, _} <- result do
|
with {:ok, follower, _followed, _} <- result do
|
||||||
reblogs
|
options = cast_params(params)
|
||||||
|> case do
|
|
||||||
false -> CommonAPI.hide_reblogs(follower, followed)
|
case reblogs_visibility(options[:reblogs], result) do
|
||||||
_ -> CommonAPI.show_reblogs(follower, followed)
|
|
||||||
end
|
|
||||||
|> case do
|
|
||||||
{:ok, follower} -> {:ok, follower}
|
{:ok, follower} -> {:ok, follower}
|
||||||
_ -> {:ok, follower}
|
_ -> {:ok, follower}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp reblogs_visibility(false, {:ok, follower, followed, _}) do
|
||||||
|
CommonAPI.hide_reblogs(follower, followed)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp reblogs_visibility(_, {:ok, follower, followed, _}) do
|
||||||
|
CommonAPI.show_reblogs(follower, followed)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec get_followers(User.t(), map()) :: list(User.t())
|
||||||
def get_followers(user, params \\ %{}) do
|
def get_followers(user, params \\ %{}) do
|
||||||
user
|
user
|
||||||
|> User.get_followers_query()
|
|> User.get_followers_query()
|
||||||
|
|
|
@ -371,6 +371,7 @@ def public_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
|> Map.put("local_only", local_only)
|
|> Map.put("local_only", local_only)
|
||||||
|> Map.put("blocking_user", user)
|
|> Map.put("blocking_user", user)
|
||||||
|> Map.put("muting_user", user)
|
|> Map.put("muting_user", user)
|
||||||
|
|> Map.put("user", user)
|
||||||
|> ActivityPub.fetch_public_activities()
|
|> ActivityPub.fetch_public_activities()
|
||||||
|> Enum.reverse()
|
|> Enum.reverse()
|
||||||
|
|
||||||
|
@ -432,12 +433,9 @@ def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
activities <-
|
activities <-
|
||||||
ActivityPub.fetch_activities_for_context(activity.data["context"], %{
|
ActivityPub.fetch_activities_for_context(activity.data["context"], %{
|
||||||
"blocking_user" => user,
|
"blocking_user" => user,
|
||||||
"user" => user
|
"user" => user,
|
||||||
|
"exclude_id" => activity.id
|
||||||
}),
|
}),
|
||||||
activities <-
|
|
||||||
activities |> Enum.filter(fn %{id: aid} -> to_string(aid) != to_string(id) end),
|
|
||||||
activities <-
|
|
||||||
activities |> Enum.filter(fn %{data: %{"type" => type}} -> type == "Create" end),
|
|
||||||
grouped_activities <- Enum.group_by(activities, fn %{id: id} -> id < activity.id end) do
|
grouped_activities <- Enum.group_by(activities, fn %{id: id} -> id < activity.id end) do
|
||||||
result = %{
|
result = %{
|
||||||
ancestors:
|
ancestors:
|
||||||
|
@ -472,8 +470,8 @@ def get_poll(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|> try_render("poll.json", %{object: object, for: user})
|
|> try_render("poll.json", %{object: object, for: user})
|
||||||
else
|
else
|
||||||
nil -> render_error(conn, :not_found, "Record not found")
|
error when is_nil(error) or error == false ->
|
||||||
false -> render_error(conn, :not_found, "Record not found")
|
render_error(conn, :not_found, "Record not found")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -821,8 +819,8 @@ def get_mascot(%{assigns: %{user: user}} = conn, _params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with %Activity{data: %{"object" => object}} <- Activity.get_by_id(id),
|
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||||
%Object{data: %{"likes" => likes}} <- Object.normalize(object) do
|
%Object{data: %{"likes" => likes}} <- Object.normalize(activity) do
|
||||||
q = from(u in User, where: u.ap_id in ^likes)
|
q = from(u in User, where: u.ap_id in ^likes)
|
||||||
|
|
||||||
users =
|
users =
|
||||||
|
@ -838,8 +836,8 @@ def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with %Activity{data: %{"object" => object}} <- Activity.get_by_id(id),
|
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||||
%Object{data: %{"announcements" => announces}} <- Object.normalize(object) do
|
%Object{data: %{"announcements" => announces}} <- Object.normalize(activity) do
|
||||||
q = from(u in User, where: u.ap_id in ^announces)
|
q = from(u in User, where: u.ap_id in ^announces)
|
||||||
|
|
||||||
users =
|
users =
|
||||||
|
@ -880,6 +878,7 @@ def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
|> Map.put("local_only", local_only)
|
|> Map.put("local_only", local_only)
|
||||||
|> Map.put("blocking_user", user)
|
|> Map.put("blocking_user", user)
|
||||||
|> Map.put("muting_user", user)
|
|> Map.put("muting_user", user)
|
||||||
|
|> Map.put("user", user)
|
||||||
|> Map.put("tag", tags)
|
|> Map.put("tag", tags)
|
||||||
|> Map.put("tag_all", tag_all)
|
|> Map.put("tag_all", tag_all)
|
||||||
|> Map.put("tag_reject", tag_reject)
|
|> Map.put("tag_reject", tag_reject)
|
||||||
|
@ -1286,6 +1285,7 @@ def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params)
|
||||||
params
|
params
|
||||||
|> Map.put("type", "Create")
|
|> Map.put("type", "Create")
|
||||||
|> Map.put("blocking_user", user)
|
|> Map.put("blocking_user", user)
|
||||||
|
|> Map.put("user", user)
|
||||||
|> Map.put("muting_user", user)
|
|> Map.put("muting_user", user)
|
||||||
|
|
||||||
# we must filter the following list for the user to avoid leaking statuses the user
|
# we must filter the following list for the user to avoid leaking statuses the user
|
||||||
|
@ -1626,45 +1626,35 @@ def suggestions(%{assigns: %{user: user}} = conn, _) do
|
||||||
|> String.replace("{{user}}", user)
|
|> String.replace("{{user}}", user)
|
||||||
|
|
||||||
with {:ok, %{status: 200, body: body}} <-
|
with {:ok, %{status: 200, body: body}} <-
|
||||||
HTTP.get(
|
HTTP.get(url, [], adapter: [recv_timeout: timeout, pool: :default]),
|
||||||
url,
|
|
||||||
[],
|
|
||||||
adapter: [
|
|
||||||
recv_timeout: timeout,
|
|
||||||
pool: :default
|
|
||||||
]
|
|
||||||
),
|
|
||||||
{:ok, data} <- Jason.decode(body) do
|
{:ok, data} <- Jason.decode(body) do
|
||||||
data =
|
data =
|
||||||
data
|
data
|
||||||
|> Enum.slice(0, limit)
|
|> Enum.slice(0, limit)
|
||||||
|> Enum.map(fn x ->
|
|> Enum.map(fn x ->
|
||||||
Map.put(
|
x
|
||||||
x,
|
|> Map.put("id", fetch_suggestion_id(x))
|
||||||
"id",
|
|> Map.put("avatar", MediaProxy.url(x["avatar"]))
|
||||||
case User.get_or_fetch(x["acct"]) do
|
|> Map.put("avatar_static", MediaProxy.url(x["avatar_static"]))
|
||||||
{:ok, %User{id: id}} -> id
|
|
||||||
_ -> 0
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end)
|
|
||||||
|> Enum.map(fn x ->
|
|
||||||
Map.put(x, "avatar", MediaProxy.url(x["avatar"]))
|
|
||||||
end)
|
|
||||||
|> Enum.map(fn x ->
|
|
||||||
Map.put(x, "avatar_static", MediaProxy.url(x["avatar_static"]))
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
conn
|
json(conn, data)
|
||||||
|> json(data)
|
|
||||||
else
|
else
|
||||||
e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
|
e ->
|
||||||
|
Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
json(conn, [])
|
json(conn, [])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp fetch_suggestion_id(attrs) do
|
||||||
|
case User.get_or_fetch(attrs["acct"]) do
|
||||||
|
{:ok, %User{id: id}} -> id
|
||||||
|
_ -> 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def status_card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do
|
def status_card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do
|
||||||
with %Activity{} = activity <- Activity.get_by_id(status_id),
|
with %Activity{} = activity <- Activity.get_by_id(status_id),
|
||||||
true <- Visibility.visible_for_user?(activity, user) do
|
true <- Visibility.visible_for_user?(activity, user) do
|
||||||
|
|
|
@ -72,6 +72,13 @@ defp do_render("account.json", %{user: user} = opts) do
|
||||||
image = User.avatar_url(user) |> MediaProxy.url()
|
image = User.avatar_url(user) |> MediaProxy.url()
|
||||||
header = User.banner_url(user) |> MediaProxy.url()
|
header = User.banner_url(user) |> MediaProxy.url()
|
||||||
user_info = User.get_cached_user_info(user)
|
user_info = User.get_cached_user_info(user)
|
||||||
|
|
||||||
|
following_count =
|
||||||
|
((!user.info.hide_follows or opts[:for] == user) && user_info.following_count) || 0
|
||||||
|
|
||||||
|
followers_count =
|
||||||
|
((!user.info.hide_followers or opts[:for] == user) && user_info.follower_count) || 0
|
||||||
|
|
||||||
bot = (user.info.source_data["type"] || "Person") in ["Application", "Service"]
|
bot = (user.info.source_data["type"] || "Person") in ["Application", "Service"]
|
||||||
|
|
||||||
emojis =
|
emojis =
|
||||||
|
@ -102,8 +109,8 @@ defp do_render("account.json", %{user: user} = opts) do
|
||||||
display_name: display_name,
|
display_name: display_name,
|
||||||
locked: user_info.locked,
|
locked: user_info.locked,
|
||||||
created_at: Utils.to_masto_date(user.inserted_at),
|
created_at: Utils.to_masto_date(user.inserted_at),
|
||||||
followers_count: user_info.follower_count,
|
followers_count: followers_count,
|
||||||
following_count: user_info.following_count,
|
following_count: following_count,
|
||||||
statuses_count: user_info.note_count,
|
statuses_count: user_info.note_count,
|
||||||
note: bio || "",
|
note: bio || "",
|
||||||
url: User.profile_url(user),
|
url: User.profile_url(user),
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
defmodule Pleroma.Web.MastodonAPI.StatusView do
|
defmodule Pleroma.Web.MastodonAPI.StatusView do
|
||||||
use Pleroma.Web, :view
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Conversation
|
alias Pleroma.Conversation
|
||||||
alias Pleroma.Conversation.Participation
|
alias Pleroma.Conversation.Participation
|
||||||
|
@ -26,19 +28,19 @@ defp get_replied_to_activities([]), do: %{}
|
||||||
defp get_replied_to_activities(activities) do
|
defp get_replied_to_activities(activities) do
|
||||||
activities
|
activities
|
||||||
|> Enum.map(fn
|
|> Enum.map(fn
|
||||||
%{data: %{"type" => "Create", "object" => object}} ->
|
%{data: %{"type" => "Create"}} = activity ->
|
||||||
object = Object.normalize(object)
|
object = Object.normalize(activity)
|
||||||
object.data["inReplyTo"] != "" && object.data["inReplyTo"]
|
object && object.data["inReplyTo"] != "" && object.data["inReplyTo"]
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
nil
|
nil
|
||||||
end)
|
end)
|
||||||
|> Enum.filter(& &1)
|
|> Enum.filter(& &1)
|
||||||
|> Activity.create_by_object_ap_id()
|
|> Activity.create_by_object_ap_id_with_object()
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|> Enum.reduce(%{}, fn activity, acc ->
|
|> Enum.reduce(%{}, fn activity, acc ->
|
||||||
object = Object.normalize(activity)
|
object = Object.normalize(activity)
|
||||||
Map.put(acc, object.data["id"], activity)
|
if object, do: Map.put(acc, object.data["id"], activity), else: acc
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -90,6 +92,7 @@ def render(
|
||||||
reblogged_activity =
|
reblogged_activity =
|
||||||
Activity.create_by_object_ap_id(activity_object.data["id"])
|
Activity.create_by_object_ap_id(activity_object.data["id"])
|
||||||
|> Activity.with_preloaded_bookmark(opts[:for])
|
|> Activity.with_preloaded_bookmark(opts[:for])
|
||||||
|
|> Activity.with_set_thread_muted_field(opts[:for])
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
|
|
||||||
reblogged = render("status.json", Map.put(opts, :activity, reblogged_activity))
|
reblogged = render("status.json", Map.put(opts, :activity, reblogged_activity))
|
||||||
|
@ -144,6 +147,7 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity
|
||||||
object = Object.normalize(activity)
|
object = Object.normalize(activity)
|
||||||
|
|
||||||
user = get_user(activity.data["actor"])
|
user = get_user(activity.data["actor"])
|
||||||
|
user_follower_address = user.follower_address
|
||||||
|
|
||||||
like_count = object.data["like_count"] || 0
|
like_count = object.data["like_count"] || 0
|
||||||
announcement_count = object.data["announcement_count"] || 0
|
announcement_count = object.data["announcement_count"] || 0
|
||||||
|
@ -159,7 +163,11 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity
|
||||||
mentions =
|
mentions =
|
||||||
(object.data["to"] ++ tag_mentions)
|
(object.data["to"] ++ tag_mentions)
|
||||||
|> Enum.uniq()
|
|> Enum.uniq()
|
||||||
|> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
|
|> Enum.map(fn
|
||||||
|
Pleroma.Constants.as_public() -> nil
|
||||||
|
^user_follower_address -> nil
|
||||||
|
ap_id -> User.get_cached_by_ap_id(ap_id)
|
||||||
|
end)
|
||||||
|> Enum.filter(& &1)
|
|> Enum.filter(& &1)
|
||||||
|> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end)
|
|> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end)
|
||||||
|
|
||||||
|
@ -170,7 +178,7 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity
|
||||||
thread_muted? =
|
thread_muted? =
|
||||||
case activity.thread_muted? do
|
case activity.thread_muted? do
|
||||||
thread_muted? when is_boolean(thread_muted?) -> thread_muted?
|
thread_muted? when is_boolean(thread_muted?) -> thread_muted?
|
||||||
nil -> CommonAPI.thread_muted?(user, activity)
|
nil -> (opts[:for] && CommonAPI.thread_muted?(opts[:for], activity)) || false
|
||||||
end
|
end
|
||||||
|
|
||||||
attachment_data = object.data["attachment"] || []
|
attachment_data = object.data["attachment"] || []
|
||||||
|
|
|
@ -34,64 +34,18 @@ def schemas(conn, _params) do
|
||||||
def raw_nodeinfo do
|
def raw_nodeinfo do
|
||||||
stats = Stats.get_stats()
|
stats = Stats.get_stats()
|
||||||
|
|
||||||
exclusions = Config.get([:instance, :mrf_transparency_exclusions])
|
|
||||||
|
|
||||||
mrf_simple =
|
|
||||||
Config.get(:mrf_simple)
|
|
||||||
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
|
|
||||||
|> Enum.into(%{})
|
|
||||||
|
|
||||||
# This horror is needed to convert regex sigils to strings
|
|
||||||
mrf_keyword =
|
|
||||||
Config.get(:mrf_keyword, [])
|
|
||||||
|> Enum.map(fn {key, value} ->
|
|
||||||
{key,
|
|
||||||
Enum.map(value, fn
|
|
||||||
{pattern, replacement} ->
|
|
||||||
%{
|
|
||||||
"pattern" =>
|
|
||||||
if not is_binary(pattern) do
|
|
||||||
inspect(pattern)
|
|
||||||
else
|
|
||||||
pattern
|
|
||||||
end,
|
|
||||||
"replacement" => replacement
|
|
||||||
}
|
|
||||||
|
|
||||||
pattern ->
|
|
||||||
if not is_binary(pattern) do
|
|
||||||
inspect(pattern)
|
|
||||||
else
|
|
||||||
pattern
|
|
||||||
end
|
|
||||||
end)}
|
|
||||||
end)
|
|
||||||
|> Enum.into(%{})
|
|
||||||
|
|
||||||
mrf_policies =
|
|
||||||
MRF.get_policies()
|
|
||||||
|> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
|
|
||||||
|
|
||||||
quarantined = Config.get([:instance, :quarantined_instances], [])
|
quarantined = Config.get([:instance, :quarantined_instances], [])
|
||||||
|
|
||||||
staff_accounts =
|
staff_accounts =
|
||||||
User.all_superusers()
|
User.all_superusers()
|
||||||
|> Enum.map(fn u -> u.ap_id end)
|
|> Enum.map(fn u -> u.ap_id end)
|
||||||
|
|
||||||
mrf_user_allowlist =
|
|
||||||
Config.get([:mrf_user_allowlist], [])
|
|
||||||
|> Enum.into(%{}, fn {k, v} -> {k, length(v)} end)
|
|
||||||
|
|
||||||
federation_response =
|
federation_response =
|
||||||
if Config.get([:instance, :mrf_transparency]) do
|
if Config.get([:instance, :mrf_transparency]) do
|
||||||
%{
|
{:ok, data} = MRF.describe()
|
||||||
mrf_policies: mrf_policies,
|
|
||||||
mrf_simple: mrf_simple,
|
data
|
||||||
mrf_keyword: mrf_keyword,
|
|> Map.merge(%{quarantined_instances: quarantined})
|
||||||
mrf_user_allowlist: mrf_user_allowlist,
|
|
||||||
quarantined_instances: quarantined,
|
|
||||||
exclusions: length(exclusions) > 0
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
%{}
|
%{}
|
||||||
end
|
end
|
||||||
|
|
|
@ -619,6 +619,8 @@ defmodule Pleroma.Web.Router do
|
||||||
post("/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request)
|
post("/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request)
|
||||||
get("/push/subscriptions/:id", Websub.WebsubController, :websub_subscription_confirmation)
|
get("/push/subscriptions/:id", Websub.WebsubController, :websub_subscription_confirmation)
|
||||||
post("/push/subscriptions/:id", Websub.WebsubController, :websub_incoming)
|
post("/push/subscriptions/:id", Websub.WebsubController, :websub_incoming)
|
||||||
|
|
||||||
|
get("/mailer/unsubscribe/:token", Mailer.SubscriptionController, :unsubscribe)
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :activitypub do
|
pipeline :activitypub do
|
||||||
|
|
20
lib/pleroma/web/templates/email/digest.html.eex
Normal file
20
lib/pleroma/web/templates/email/digest.html.eex
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<h1>Hey <%= @user.nickname %>, here is what you've missed!</h1>
|
||||||
|
|
||||||
|
<h2>New Mentions:</h2>
|
||||||
|
<ul>
|
||||||
|
<%= for %{data: mention, object: object, from: from} <- @mentions do %>
|
||||||
|
<li><%= link from.nickname, to: mention.activity.actor %>: <%= raw object.data["content"] %></li>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<%= if @followers != [] do %>
|
||||||
|
<h2><%= length(@followers) %> New Followers:</h2>
|
||||||
|
<ul>
|
||||||
|
<%= for %{data: follow, from: from} <- @followers do %>
|
||||||
|
<li><%= link from.nickname, to: follow.activity.actor %></li>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<p>You have received this email because you have signed up to receive digest emails from <b><%= @instance %></b> Pleroma instance.</p>
|
||||||
|
<p>The email address you are subscribed as is <%= @user.email %>. To unsubscribe, please go <%= link "here", to: @unsubscribe_link %>.</p>
|
10
lib/pleroma/web/templates/layout/email.html.eex
Normal file
10
lib/pleroma/web/templates/layout/email.html.eex
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title><%= @email.subject %></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<%= render @view_module, @view_template, assigns %>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1 @@
|
||||||
|
<h1>UNSUBSCRIBE FAILURE</h1>
|
|
@ -0,0 +1 @@
|
||||||
|
<h1>UNSUBSCRIBE SUCCESSFUL</h1>
|
5
lib/pleroma/web/views/email_view.ex
Normal file
5
lib/pleroma/web/views/email_view.ex
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
defmodule Pleroma.Web.EmailView do
|
||||||
|
use Pleroma.Web, :view
|
||||||
|
import Phoenix.HTML
|
||||||
|
import Phoenix.HTML.Link
|
||||||
|
end
|
3
lib/pleroma/web/views/mailer/subscription_view.ex
Normal file
3
lib/pleroma/web/views/mailer/subscription_view.ex
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule Pleroma.Web.Mailer.SubscriptionView do
|
||||||
|
use Pleroma.Web, :view
|
||||||
|
end
|
|
@ -58,10 +58,10 @@ def safe_render(view, template, assigns \\ %{}) do
|
||||||
rescue
|
rescue
|
||||||
error ->
|
error ->
|
||||||
Logger.error(
|
Logger.error(
|
||||||
"#{__MODULE__} failed to render #{inspect({view, template})}: #{inspect(error)}"
|
"#{__MODULE__} failed to render #{inspect({view, template})}\n" <>
|
||||||
|
Exception.format(:error, error, __STACKTRACE__)
|
||||||
)
|
)
|
||||||
|
|
||||||
Logger.error(inspect(__STACKTRACE__))
|
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
7
mix.exs
7
mix.exs
|
@ -114,8 +114,9 @@ defp deps do
|
||||||
{:tesla, "~> 1.2"},
|
{:tesla, "~> 1.2"},
|
||||||
{:jason, "~> 1.0"},
|
{:jason, "~> 1.0"},
|
||||||
{:mogrify, "~> 0.6.1"},
|
{:mogrify, "~> 0.6.1"},
|
||||||
{:ex_aws, "~> 2.0"},
|
{:ex_aws, "~> 2.1"},
|
||||||
{:ex_aws_s3, "~> 2.0"},
|
{:ex_aws_s3, "~> 2.0"},
|
||||||
|
{:sweet_xml, "~> 0.6.6"},
|
||||||
{:earmark, "~> 1.3"},
|
{:earmark, "~> 1.3"},
|
||||||
{:bbcode, "~> 0.1.1"},
|
{:bbcode, "~> 0.1.1"},
|
||||||
{:ex_machina, "~> 2.3", only: :test},
|
{:ex_machina, "~> 2.3", only: :test},
|
||||||
|
@ -127,6 +128,7 @@ defp deps do
|
||||||
{:ex_doc, "~> 0.20.2", only: :dev, runtime: false},
|
{:ex_doc, "~> 0.20.2", only: :dev, runtime: false},
|
||||||
{:web_push_encryption, "~> 0.2.1"},
|
{:web_push_encryption, "~> 0.2.1"},
|
||||||
{:swoosh, "~> 0.23.2"},
|
{:swoosh, "~> 0.23.2"},
|
||||||
|
{:phoenix_swoosh, "~> 0.2"},
|
||||||
{:gen_smtp, "~> 0.13"},
|
{:gen_smtp, "~> 0.13"},
|
||||||
{:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test},
|
{:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test},
|
||||||
{:floki, "~> 0.20.0"},
|
{:floki, "~> 0.20.0"},
|
||||||
|
@ -139,7 +141,7 @@ defp deps do
|
||||||
{:http_signatures,
|
{:http_signatures,
|
||||||
git: "https://git.pleroma.social/pleroma/http_signatures.git",
|
git: "https://git.pleroma.social/pleroma/http_signatures.git",
|
||||||
ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"},
|
ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"},
|
||||||
{:pleroma_job_queue, "~> 0.2.0"},
|
{:pleroma_job_queue, "~> 0.3"},
|
||||||
{:telemetry, "~> 0.3"},
|
{:telemetry, "~> 0.3"},
|
||||||
{:prometheus_ex, "~> 3.0"},
|
{:prometheus_ex, "~> 3.0"},
|
||||||
{:prometheus_plugs, "~> 1.1"},
|
{:prometheus_plugs, "~> 1.1"},
|
||||||
|
@ -147,6 +149,7 @@ defp deps do
|
||||||
{:prometheus_ecto, "~> 1.4"},
|
{:prometheus_ecto, "~> 1.4"},
|
||||||
{:recon, github: "ferd/recon", tag: "2.4.0"},
|
{:recon, github: "ferd/recon", tag: "2.4.0"},
|
||||||
{:quack, "~> 0.1.1"},
|
{:quack, "~> 0.1.1"},
|
||||||
|
{:joken, "~> 2.0"},
|
||||||
{:benchee, "~> 1.0"},
|
{:benchee, "~> 1.0"},
|
||||||
{:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)},
|
{:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)},
|
||||||
{:ex_rated, "~> 1.3"},
|
{:ex_rated, "~> 1.3"},
|
||||||
|
|
18
mix.lock
18
mix.lock
|
@ -5,16 +5,17 @@
|
||||||
"bbcode": {:hex, :bbcode, "0.1.1", "0023e2c7814119b2e620b7add67182e3f6019f92bfec9a22da7e99821aceba70", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
|
"bbcode": {:hex, :bbcode, "0.1.1", "0023e2c7814119b2e620b7add67182e3f6019f92bfec9a22da7e99821aceba70", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm"},
|
"benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
|
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
|
||||||
"cachex": {:hex, :cachex, "3.0.2", "1351caa4e26e29f7d7ec1d29b53d6013f0447630bbf382b4fb5d5bad0209f203", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"},
|
"cachex": {:hex, :cachex, "3.0.3", "4e2d3e05814a5738f5ff3903151d5c25636d72a3527251b753f501ad9c657967", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"calendar": {:hex, :calendar, "0.17.6", "ec291cb2e4ba499c2e8c0ef5f4ace974e2f9d02ae9e807e711a9b0c7850b9aee", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
|
"calendar": {:hex, :calendar, "0.17.6", "ec291cb2e4ba499c2e8c0ef5f4ace974e2f9d02ae9e807e711a9b0c7850b9aee", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
|
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"},
|
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"},
|
||||||
"comeonin": {:hex, :comeonin, "4.1.1", "c7304fc29b45b897b34142a91122bc72757bc0c295e9e824999d5179ffc08416", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm"},
|
"comeonin": {:hex, :comeonin, "4.1.2", "3eb5620fd8e35508991664b4c2b04dd41e52f1620b36957be837c1d7784b7592", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
|
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
|
||||||
"cors_plug": {:hex, :cors_plug, "1.5.2", "72df63c87e4f94112f458ce9d25800900cc88608c1078f0e4faddf20933eda6e", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
"cors_plug": {:hex, :cors_plug, "1.5.2", "72df63c87e4f94112f458ce9d25800900cc88608c1078f0e4faddf20933eda6e", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"cowboy": {:hex, :cowboy, "2.6.3", "99aa50e94e685557cad82e704457336a453d4abcb77839ad22dbe71f311fcc06", [:rebar3], [{:cowlib, "~> 2.7.3", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
|
"cowboy": {:hex, :cowboy, "2.6.3", "99aa50e94e685557cad82e704457336a453d4abcb77839ad22dbe71f311fcc06", [:rebar3], [{:cowlib, "~> 2.7.3", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"cowlib": {:hex, :cowlib, "2.7.3", "a7ffcd0917e6d50b4d5fb28e9e2085a0ceb3c97dea310505f7460ff5ed764ce9", [:rebar3], [], "hexpm"},
|
"cowlib": {:hex, :cowlib, "2.7.3", "a7ffcd0917e6d50b4d5fb28e9e2085a0ceb3c97dea310505f7460ff5ed764ce9", [:rebar3], [], "hexpm"},
|
||||||
"credo": {:hex, :credo, "0.9.3", "76fa3e9e497ab282e0cf64b98a624aa11da702854c52c82db1bf24e54ab7c97a", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
|
"credo": {:hex, :credo, "0.9.3", "76fa3e9e497ab282e0cf64b98a624aa11da702854c52c82db1bf24e54ab7c97a", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
"crontab": {:hex, :crontab, "1.1.7", "b9219f0bdc8678b94143655a8f229716c5810c0636a4489f98c0956137e53985", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
"crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]},
|
"crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]},
|
||||||
"db_connection": {:hex, :db_connection, "2.0.6", "bde2f85d047969c5b5800cb8f4b3ed6316c8cb11487afedac4aa5f93fd39abfa", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"},
|
"db_connection": {:hex, :db_connection, "2.0.6", "bde2f85d047969c5b5800cb8f4b3ed6316c8cb11487afedac4aa5f93fd39abfa", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm"},
|
"decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm"},
|
||||||
|
@ -43,14 +44,15 @@
|
||||||
"httpoison": {:hex, :httpoison, "1.2.0", "2702ed3da5fd7a8130fc34b11965c8cfa21ade2f232c00b42d96d4967c39a3a3", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
"httpoison": {:hex, :httpoison, "1.2.0", "2702ed3da5fd7a8130fc34b11965c8cfa21ade2f232c00b42d96d4967c39a3a3", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
|
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
|
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
"jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
|
"joken": {:hex, :joken, "2.0.1", "ec9ab31bf660f343380da033b3316855197c8d4c6ef597fa3fcb451b326beb14", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
"jose": {:hex, :jose, "1.9.0", "4167c5f6d06ffaebffd15cdb8da61a108445ef5e85ab8f5a7ad926fdf3ada154", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
|
"makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
|
"makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm"},
|
"meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm"},
|
||||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
|
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
|
||||||
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
|
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
|
||||||
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
|
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
|
||||||
"mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"},
|
"mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"},
|
||||||
"mock": {:hex, :mock, "0.3.3", "42a433794b1291a9cf1525c6d26b38e039e0d3a360732b5e467bfc77ef26c914", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
|
"mock": {:hex, :mock, "0.3.3", "42a433794b1291a9cf1525c6d26b38e039e0d3a360732b5e467bfc77ef26c914", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"},
|
"mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"},
|
||||||
"mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm"},
|
"mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm"},
|
||||||
|
@ -61,24 +63,24 @@
|
||||||
"phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
"phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"phoenix_html": {:hex, :phoenix_html, "2.13.1", "fa8f034b5328e2dfa0e4131b5569379003f34bc1fafdaa84985b0b9d2f12e68b", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
"phoenix_html": {:hex, :phoenix_html, "2.13.1", "fa8f034b5328e2dfa0e4131b5569379003f34bc1fafdaa84985b0b9d2f12e68b", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm"},
|
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm"},
|
||||||
"pleroma_job_queue": {:hex, :pleroma_job_queue, "0.2.0", "879e660aa1cebe8dc6f0aaaa6aa48b4875e89cd961d4a585fd128e0773b31a18", [:mix], [], "hexpm"},
|
"phoenix_swoosh": {:hex, :phoenix_swoosh, "0.2.0", "a7e0b32077cd6d2323ae15198839b05d9caddfa20663fd85787479e81f89520e", [:mix], [{:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 0.1", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
"pleroma_job_queue": {:hex, :pleroma_job_queue, "0.3.0", "b84538d621f0c3d6fcc1cff9d5648d3faaf873b8b21b94e6503428a07a48ec47", [:mix], [{:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"plug": {:hex, :plug, "1.8.2", "0bcce1daa420f189a6491f3940cc77ea7fb1919761175c9c3b59800d897440fc", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"},
|
"plug": {:hex, :plug, "1.8.2", "0bcce1daa420f189a6491f3940cc77ea7fb1919761175c9c3b59800d897440fc", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
"plug_cowboy": {:hex, :plug_cowboy, "2.1.0", "b75768153c3a8a9e8039d4b25bb9b14efbc58e9c4a6e6a270abff1cd30cbe320", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
"plug_cowboy": {:hex, :plug_cowboy, "2.1.0", "b75768153c3a8a9e8039d4b25bb9b14efbc58e9c4a6e6a270abff1cd30cbe320", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
|
"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"},
|
"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"},
|
"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"},
|
"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.4.1", "1e96073b3ed7788053768fea779cbc896ddc3bdd9ba60687f2ad50b252ac87d6", [:mix, :rebar3], [], "hexpm"},
|
"prometheus": {:hex, :prometheus, "4.4.1", "1e96073b3ed7788053768fea779cbc896ddc3bdd9ba60687f2ad50b252ac87d6", [: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_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_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.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [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_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [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_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
"prometheus_process_collector": {:hex, :prometheus_process_collector, "1.4.0", "6dbd39e3165b9ef1c94a7a820e9ffe08479f949dcdd431ed4aaea7b250eebfde", [:rebar3], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
|
|
||||||
"quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm"},
|
"quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
|
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
|
||||||
"recon": {:git, "https://github.com/ferd/recon.git", "75d70c7c08926d2f24f1ee6de14ee50fe8a52763", [tag: "2.4.0"]},
|
"recon": {:git, "https://github.com/ferd/recon.git", "75d70c7c08926d2f24f1ee6de14ee50fe8a52763", [tag: "2.4.0"]},
|
||||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
|
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
|
||||||
|
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm"},
|
||||||
"swoosh": {:hex, :swoosh, "0.23.2", "7dda95ff0bf54a2298328d6899c74dae1223777b43563ccebebb4b5d2b61df38", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"},
|
"swoosh": {:hex, :swoosh, "0.23.2", "7dda95ff0bf54a2298328d6899c74dae1223777b43563ccebebb4b5d2b61df38", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
"syslog": {:git, "https://github.com/Vagabond/erlang-syslog.git", "4a6c6f2c996483e86c1320e9553f91d337bcb6aa", [tag: "1.0.5"]},
|
"syslog": {:git, "https://github.com/Vagabond/erlang-syslog.git", "4a6c6f2c996483e86c1320e9553f91d337bcb6aa", [tag: "1.0.5"]},
|
||||||
"telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"},
|
"telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"},
|
||||||
|
@ -88,7 +90,7 @@
|
||||||
"tzdata": {:hex, :tzdata, "1.0.1", "f6027a331af7d837471248e62733c6ebee86a72e57c613aa071ebb1f750fc71a", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
"tzdata": {:hex, :tzdata, "1.0.1", "f6027a331af7d837471248e62733c6ebee86a72e57c613aa071ebb1f750fc71a", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"ueberauth": {:hex, :ueberauth, "0.6.1", "9e90d3337dddf38b1ca2753aca9b1e53d8a52b890191cdc55240247c89230412", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
"ueberauth": {:hex, :ueberauth, "0.6.1", "9e90d3337dddf38b1ca2753aca9b1e53d8a52b890191cdc55240247c89230412", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
|
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
|
||||||
"unsafe": {:hex, :unsafe, "1.0.0", "7c21742cd05380c7875546b023481d3a26f52df8e5dfedcb9f958f322baae305", [:mix], [], "hexpm"},
|
"unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm"},
|
||||||
"web_push_encryption": {:hex, :web_push_encryption, "0.2.1", "d42cecf73420d9dc0053ba3299cc8c8d6ff2be2487d67ca2a57265868e4d9a98", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
|
"web_push_encryption": {:hex, :web_push_encryption, "0.2.1", "d42cecf73420d9dc0053ba3299cc8c8d6ff2be2487d67ca2a57265868e4d9a98", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []},
|
"websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []},
|
||||||
}
|
}
|
||||||
|
|
20
priv/repo/migrations/20190412052952_add_user_info_fields.exs
Normal file
20
priv/repo/migrations/20190412052952_add_user_info_fields.exs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.AddEmailNotificationsToUserInfo do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("
|
||||||
|
UPDATE users
|
||||||
|
SET info = info || '{
|
||||||
|
\"email_notifications\": {
|
||||||
|
\"digest\": false
|
||||||
|
}
|
||||||
|
}'")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("
|
||||||
|
UPDATE users
|
||||||
|
SET info = info - 'email_notifications'
|
||||||
|
")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,9 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.AddSigninAndLastDigestDatesToUser do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:users) do
|
||||||
|
add(:last_digest_emailed_at, :naive_datetime, default: fragment("now()"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
BIN
priv/static/adminfe/chunk-0e18.e12401fb.css
Normal file
BIN
priv/static/adminfe/chunk-0e18.e12401fb.css
Normal file
Binary file not shown.
Binary file not shown.
BIN
priv/static/adminfe/chunk-5e57.ac97b15a.css
Normal file
BIN
priv/static/adminfe/chunk-5e57.ac97b15a.css
Normal file
Binary file not shown.
Binary file not shown.
BIN
priv/static/adminfe/chunk-elementUI.e5cd8da6.css
Normal file
BIN
priv/static/adminfe/chunk-elementUI.e5cd8da6.css
Normal file
Binary file not shown.
Binary file not shown.
|
@ -1 +1 @@
|
||||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=renderer content=webkit><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><title>Admin FE</title><link rel="shortcut icon" href=favicon.ico><link href=chunk-elementUI.f74c256b.css rel=stylesheet><link href=chunk-libs.4e8c4664.css rel=stylesheet><link href=app.34fc670f.css rel=stylesheet></head><body><script src=/pleroma/admin/static/tinymce4.7.5/tinymce.min.js></script><div id=app></div><script type=text/javascript src=static/js/runtime.d8d12c12.js></script><script type=text/javascript src=static/js/chunk-elementUI.1fa5434b.js></script><script type=text/javascript src=static/js/chunk-libs.d5609760.js></script><script type=text/javascript src=static/js/app.4137ad8f.js></script></body></html>
|
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=renderer content=webkit><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><title>Admin FE</title><link rel="shortcut icon" href=favicon.ico><link href=chunk-elementUI.e5cd8da6.css rel=stylesheet><link href=chunk-libs.4e8c4664.css rel=stylesheet><link href=app.34fc670f.css rel=stylesheet></head><body><script src=/pleroma/admin/static/tinymce4.7.5/tinymce.min.js></script><div id=app></div><script type=text/javascript src=static/js/runtime.f40c8ec4.js></script><script type=text/javascript src=static/js/chunk-elementUI.1911151b.js></script><script type=text/javascript src=static/js/chunk-libs.fb0b7f4a.js></script><script type=text/javascript src=static/js/app.8e186193.js></script></body></html>
|
Binary file not shown.
BIN
priv/static/adminfe/static/fonts/element-icons.535877f.woff
Normal file
BIN
priv/static/adminfe/static/fonts/element-icons.535877f.woff
Normal file
Binary file not shown.
Binary file not shown.
BIN
priv/static/adminfe/static/fonts/element-icons.732389d.ttf
Normal file
BIN
priv/static/adminfe/static/fonts/element-icons.732389d.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
priv/static/adminfe/static/js/app.8e186193.js
Normal file
BIN
priv/static/adminfe/static/js/app.8e186193.js
Normal file
Binary file not shown.
BIN
priv/static/adminfe/static/js/chunk-0e18.208cd826.js
Normal file
BIN
priv/static/adminfe/static/js/chunk-0e18.208cd826.js
Normal file
Binary file not shown.
BIN
priv/static/adminfe/static/js/chunk-1fbf.616fb309.js
Normal file
BIN
priv/static/adminfe/static/js/chunk-1fbf.616fb309.js
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
priv/static/adminfe/static/js/chunk-5e57.7313703a.js
Normal file
BIN
priv/static/adminfe/static/js/chunk-5e57.7313703a.js
Normal file
Binary file not shown.
Binary file not shown.
BIN
priv/static/adminfe/static/js/chunk-7fe2.458f9da5.js
Normal file
BIN
priv/static/adminfe/static/js/chunk-7fe2.458f9da5.js
Normal file
Binary file not shown.
BIN
priv/static/adminfe/static/js/chunk-e547.d57d1b91.js
Normal file
BIN
priv/static/adminfe/static/js/chunk-e547.d57d1b91.js
Normal file
Binary file not shown.
BIN
priv/static/adminfe/static/js/chunk-elementUI.1911151b.js
Normal file
BIN
priv/static/adminfe/static/js/chunk-elementUI.1911151b.js
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
priv/static/adminfe/static/js/runtime.f40c8ec4.js
Normal file
BIN
priv/static/adminfe/static/js/runtime.f40c8ec4.js
Normal file
Binary file not shown.
|
@ -68,3 +68,5 @@ config :pleroma, Pleroma.Uploaders.Local, uploads: "<%= uploads_dir %>"
|
||||||
# For using third-party S3 clones like wasabi, also do:
|
# For using third-party S3 clones like wasabi, also do:
|
||||||
# config :ex_aws, :s3,
|
# config :ex_aws, :s3,
|
||||||
# host: "s3.wasabisys.com"
|
# host: "s3.wasabisys.com"
|
||||||
|
|
||||||
|
config :joken, default_signer: "<%= jwt_secret %>"
|
||||||
|
|
51
test/mix/tasks/pleroma.digest_test.exs
Normal file
51
test/mix/tasks/pleroma.digest_test.exs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
defmodule Mix.Tasks.Pleroma.DigestTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
import Swoosh.TestAssertions
|
||||||
|
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
setup_all do
|
||||||
|
Mix.shell(Mix.Shell.Process)
|
||||||
|
|
||||||
|
on_exit(fn ->
|
||||||
|
Mix.shell(Mix.Shell.IO)
|
||||||
|
end)
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "pleroma.digest test" do
|
||||||
|
test "Sends digest to the given user" do
|
||||||
|
user1 = insert(:user)
|
||||||
|
user2 = insert(:user)
|
||||||
|
|
||||||
|
Enum.each(0..10, fn i ->
|
||||||
|
{:ok, _activity} =
|
||||||
|
CommonAPI.post(user1, %{
|
||||||
|
"status" => "hey ##{i} @#{user2.nickname}!"
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
yesterday =
|
||||||
|
NaiveDateTime.add(
|
||||||
|
NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second),
|
||||||
|
-60 * 60 * 24,
|
||||||
|
:second
|
||||||
|
)
|
||||||
|
|
||||||
|
{:ok, yesterday_date} = Timex.format(yesterday, "%F", :strftime)
|
||||||
|
|
||||||
|
:ok = Mix.Tasks.Pleroma.Digest.run(["test", user2.nickname, yesterday_date])
|
||||||
|
|
||||||
|
assert_receive {:mix_shell, :info, [message]}
|
||||||
|
assert message =~ "Digest email have been sent"
|
||||||
|
|
||||||
|
assert_email_sent(
|
||||||
|
to: {user2.name, user2.email},
|
||||||
|
html_body: ~r/new mentions:/i
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,13 +4,15 @@
|
||||||
|
|
||||||
defmodule Pleroma.NotificationTest do
|
defmodule Pleroma.NotificationTest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.Streamer
|
alias Pleroma.Web.Streamer
|
||||||
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
||||||
import Pleroma.Factory
|
|
||||||
|
|
||||||
describe "create_notifications" do
|
describe "create_notifications" do
|
||||||
test "notifies someone when they are directly addressed" do
|
test "notifies someone when they are directly addressed" do
|
||||||
|
@ -352,6 +354,51 @@ test "it sets all notifications as read up to a specified notification ID" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "for_user_since/2" do
|
||||||
|
defp days_ago(days) do
|
||||||
|
NaiveDateTime.add(
|
||||||
|
NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second),
|
||||||
|
-days * 60 * 60 * 24,
|
||||||
|
:second
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Returns recent notifications" do
|
||||||
|
user1 = insert(:user)
|
||||||
|
user2 = insert(:user)
|
||||||
|
|
||||||
|
Enum.each(0..10, fn i ->
|
||||||
|
{:ok, _activity} =
|
||||||
|
CommonAPI.post(user1, %{
|
||||||
|
"status" => "hey ##{i} @#{user2.nickname}!"
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
{old, new} = Enum.split(Notification.for_user(user2), 5)
|
||||||
|
|
||||||
|
Enum.each(old, fn notification ->
|
||||||
|
notification
|
||||||
|
|> cast(%{updated_at: days_ago(10)}, [:updated_at])
|
||||||
|
|> Pleroma.Repo.update!()
|
||||||
|
end)
|
||||||
|
|
||||||
|
recent_notifications_ids =
|
||||||
|
user2
|
||||||
|
|> Notification.for_user_since(
|
||||||
|
NaiveDateTime.add(NaiveDateTime.utc_now(), -5 * 86_400, :second)
|
||||||
|
)
|
||||||
|
|> Enum.map(& &1.id)
|
||||||
|
|
||||||
|
Enum.each(old, fn %{id: id} ->
|
||||||
|
refute id in recent_notifications_ids
|
||||||
|
end)
|
||||||
|
|
||||||
|
Enum.each(new, fn %{id: id} ->
|
||||||
|
assert id in recent_notifications_ids
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "notification target determination" do
|
describe "notification target determination" do
|
||||||
test "it sends notifications to addressed users in new messages" do
|
test "it sends notifications to addressed users in new messages" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
|
@ -9,7 +9,8 @@ def build(data \\ %{}) do
|
||||||
nickname: "testname",
|
nickname: "testname",
|
||||||
password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
|
password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
|
||||||
bio: "A tester.",
|
bio: "A tester.",
|
||||||
ap_id: "some id"
|
ap_id: "some id",
|
||||||
|
last_digest_emailed_at: NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
|
||||||
}
|
}
|
||||||
|
|
||||||
Map.merge(user, data)
|
Map.merge(user, data)
|
||||||
|
|
|
@ -31,7 +31,8 @@ def user_factory do
|
||||||
nickname: sequence(:nickname, &"nick#{&1}"),
|
nickname: sequence(:nickname, &"nick#{&1}"),
|
||||||
password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
|
password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
|
||||||
bio: sequence(:bio, &"Tester Number #{&1}"),
|
bio: sequence(:bio, &"Tester Number #{&1}"),
|
||||||
info: %{}
|
info: %{},
|
||||||
|
last_digest_emailed_at: NaiveDateTime.utc_now()
|
||||||
}
|
}
|
||||||
|
|
||||||
%{
|
%{
|
||||||
|
|
13
test/support/mrf_module_mock.ex
Normal file
13
test/support/mrf_module_mock.ex
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule MRFModuleMock do
|
||||||
|
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{mrf_module_mock: "some config data"}}
|
||||||
|
end
|
|
@ -3,8 +3,11 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Mix.Tasks.Pleroma.DatabaseTest do
|
defmodule Mix.Tasks.Pleroma.DatabaseTest do
|
||||||
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
@ -46,4 +49,37 @@ test "following and followers count are updated" do
|
||||||
assert user.info.follower_count == 0
|
assert user.info.follower_count == 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "running fix_likes_collections" do
|
||||||
|
test "it turns OrderedCollection likes into empty arrays" do
|
||||||
|
[user, user2] = insert_pair(:user)
|
||||||
|
|
||||||
|
{:ok, %{id: id, object: object}} = CommonAPI.post(user, %{"status" => "test"})
|
||||||
|
{:ok, %{object: object2}} = CommonAPI.post(user, %{"status" => "test test"})
|
||||||
|
|
||||||
|
CommonAPI.favorite(id, user2)
|
||||||
|
|
||||||
|
likes = %{
|
||||||
|
"first" =>
|
||||||
|
"http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes?page=1",
|
||||||
|
"id" => "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes",
|
||||||
|
"totalItems" => 3,
|
||||||
|
"type" => "OrderedCollection"
|
||||||
|
}
|
||||||
|
|
||||||
|
new_data = Map.put(object2.data, "likes", likes)
|
||||||
|
|
||||||
|
object2
|
||||||
|
|> Ecto.Changeset.change(%{data: new_data})
|
||||||
|
|> Repo.update()
|
||||||
|
|
||||||
|
assert length(Object.get_by_id(object.id).data["likes"]) == 1
|
||||||
|
assert is_map(Object.get_by_id(object2.id).data["likes"])
|
||||||
|
|
||||||
|
assert :ok == Mix.Tasks.Pleroma.Database.run(["fix_likes_collections"])
|
||||||
|
|
||||||
|
assert length(Object.get_by_id(object.id).data["likes"]) == 1
|
||||||
|
assert Enum.empty?(Object.get_by_id(object2.id).data["likes"])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
32
test/uploaders/local_test.exs
Normal file
32
test/uploaders/local_test.exs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Uploaders.LocalTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
alias Pleroma.Uploaders.Local
|
||||||
|
|
||||||
|
describe "get_file/1" do
|
||||||
|
test "it returns path to local folder for files" do
|
||||||
|
assert Local.get_file("") == {:ok, {:static_dir, "test/uploads"}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "put_file/1" do
|
||||||
|
test "put file to local folder" do
|
||||||
|
file_path = "local_upload/files/image.jpg"
|
||||||
|
|
||||||
|
file = %Pleroma.Upload{
|
||||||
|
name: "image.jpg",
|
||||||
|
content_type: "image/jpg",
|
||||||
|
path: file_path,
|
||||||
|
tempfile: Path.absname("test/fixtures/image_tmp.jpg")
|
||||||
|
}
|
||||||
|
|
||||||
|
assert Local.put_file(file) == :ok
|
||||||
|
|
||||||
|
assert Path.join([Local.upload_path(), file_path])
|
||||||
|
|> File.exists?()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
50
test/uploaders/mdii_test.exs
Normal file
50
test/uploaders/mdii_test.exs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Uploaders.MDIITest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
alias Pleroma.Uploaders.MDII
|
||||||
|
import Tesla.Mock
|
||||||
|
|
||||||
|
describe "get_file/1" do
|
||||||
|
test "it returns path to local folder for files" do
|
||||||
|
assert MDII.get_file("") == {:ok, {:static_dir, "test/uploads"}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "put_file/1" do
|
||||||
|
setup do
|
||||||
|
file_upload = %Pleroma.Upload{
|
||||||
|
name: "mdii-image.jpg",
|
||||||
|
content_type: "image/jpg",
|
||||||
|
path: "test_folder/mdii-image.jpg",
|
||||||
|
tempfile: Path.absname("test/fixtures/image_tmp.jpg")
|
||||||
|
}
|
||||||
|
|
||||||
|
[file_upload: file_upload]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "save file", %{file_upload: file_upload} do
|
||||||
|
mock(fn
|
||||||
|
%{method: :post, url: "https://mdii.sakura.ne.jp/mdii-post.cgi?jpg"} ->
|
||||||
|
%Tesla.Env{status: 200, body: "mdii-image"}
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert MDII.put_file(file_upload) ==
|
||||||
|
{:ok, {:url, "https://mdii.sakura.ne.jp/mdii-image.jpg"}}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "save file to local if MDII isn`t available", %{file_upload: file_upload} do
|
||||||
|
mock(fn
|
||||||
|
%{method: :post, url: "https://mdii.sakura.ne.jp/mdii-post.cgi?jpg"} ->
|
||||||
|
%Tesla.Env{status: 500}
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert MDII.put_file(file_upload) == :ok
|
||||||
|
|
||||||
|
assert Path.join([Pleroma.Uploaders.Local.upload_path(), file_upload.path])
|
||||||
|
|> File.exists?()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
90
test/uploaders/s3_test.exs
Normal file
90
test/uploaders/s3_test.exs
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Uploaders.S3Test do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.Uploaders.S3
|
||||||
|
|
||||||
|
import Mock
|
||||||
|
import ExUnit.CaptureLog
|
||||||
|
|
||||||
|
setup do
|
||||||
|
config = Config.get([Pleroma.Uploaders.S3])
|
||||||
|
|
||||||
|
Config.put([Pleroma.Uploaders.S3],
|
||||||
|
bucket: "test_bucket",
|
||||||
|
public_endpoint: "https://s3.amazonaws.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
on_exit(fn ->
|
||||||
|
Config.put([Pleroma.Uploaders.S3], config)
|
||||||
|
end)
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "get_file/1" do
|
||||||
|
test "it returns path to local folder for files" do
|
||||||
|
assert S3.get_file("test_image.jpg") == {
|
||||||
|
:ok,
|
||||||
|
{:url, "https://s3.amazonaws.com/test_bucket/test_image.jpg"}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it returns path without bucket when truncated_namespace set to ''" do
|
||||||
|
Config.put([Pleroma.Uploaders.S3],
|
||||||
|
bucket: "test_bucket",
|
||||||
|
public_endpoint: "https://s3.amazonaws.com",
|
||||||
|
truncated_namespace: ""
|
||||||
|
)
|
||||||
|
|
||||||
|
assert S3.get_file("test_image.jpg") == {
|
||||||
|
:ok,
|
||||||
|
{:url, "https://s3.amazonaws.com/test_image.jpg"}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it returns path with bucket namespace when namespace is set" do
|
||||||
|
Config.put([Pleroma.Uploaders.S3],
|
||||||
|
bucket: "test_bucket",
|
||||||
|
public_endpoint: "https://s3.amazonaws.com",
|
||||||
|
bucket_namespace: "family"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert S3.get_file("test_image.jpg") == {
|
||||||
|
:ok,
|
||||||
|
{:url, "https://s3.amazonaws.com/family:test_bucket/test_image.jpg"}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "put_file/1" do
|
||||||
|
setup do
|
||||||
|
file_upload = %Pleroma.Upload{
|
||||||
|
name: "image-tet.jpg",
|
||||||
|
content_type: "image/jpg",
|
||||||
|
path: "test_folder/image-tet.jpg",
|
||||||
|
tempfile: Path.absname("test/fixtures/image_tmp.jpg")
|
||||||
|
}
|
||||||
|
|
||||||
|
[file_upload: file_upload]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "save file", %{file_upload: file_upload} do
|
||||||
|
with_mock ExAws, request: fn _ -> {:ok, :ok} end do
|
||||||
|
assert S3.put_file(file_upload) == {:ok, {:file, "test_folder/image-tet.jpg"}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns error", %{file_upload: file_upload} do
|
||||||
|
with_mock ExAws, request: fn _ -> {:error, "S3 Upload failed"} end do
|
||||||
|
assert capture_log(fn ->
|
||||||
|
assert S3.put_file(file_upload) == {:error, "S3 Upload failed"}
|
||||||
|
end) =~ "Elixir.Pleroma.Uploaders.S3: {:error, \"S3 Upload failed\"}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
24
test/user_info_test.exs
Normal file
24
test/user_info_test.exs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
defmodule Pleroma.UserInfoTest do
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.User.Info
|
||||||
|
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
describe "update_email_notifications/2" do
|
||||||
|
setup do
|
||||||
|
user = insert(:user, %{info: %{email_notifications: %{"digest" => true}}})
|
||||||
|
|
||||||
|
{:ok, user: user}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Notifications are updated", %{user: user} do
|
||||||
|
true = user.info.email_notifications["digest"]
|
||||||
|
changeset = Info.update_email_notifications(user.info, %{"digest" => false})
|
||||||
|
assert changeset.valid?
|
||||||
|
{:ok, result} = Ecto.Changeset.apply_action(changeset, :insert)
|
||||||
|
assert result.email_notifications["digest"] == false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -193,7 +193,14 @@ test "works with URIs" do
|
||||||
user = User.get_cached_by_ap_id("http://mastodon.example.org/users/admin")
|
user = User.get_cached_by_ap_id("http://mastodon.example.org/users/admin")
|
||||||
|
|
||||||
assert length(results) == 1
|
assert length(results) == 1
|
||||||
assert user == result |> Map.put(:search_rank, nil) |> Map.put(:search_type, nil)
|
|
||||||
|
expected =
|
||||||
|
result
|
||||||
|
|> Map.put(:search_rank, nil)
|
||||||
|
|> Map.put(:search_type, nil)
|
||||||
|
|> Map.put(:last_digest_emailed_at, nil)
|
||||||
|
|
||||||
|
assert user == expected
|
||||||
end
|
end
|
||||||
|
|
||||||
test "excludes a blocked users from search result" do
|
test "excludes a blocked users from search result" do
|
||||||
|
|
|
@ -525,7 +525,10 @@ test "it has required fields" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it restricts some sizes" do
|
test "it restricts some sizes" do
|
||||||
[bio: 5000, name: 100]
|
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
||||||
|
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
||||||
|
|
||||||
|
[bio: bio_limit, name: name_limit]
|
||||||
|> Enum.each(fn {field, size} ->
|
|> Enum.each(fn {field, size} ->
|
||||||
string = String.pad_leading(".", size)
|
string = String.pad_leading(".", size)
|
||||||
cs = User.remote_user_creation(Map.put(@valid_remote, field, string))
|
cs = User.remote_user_creation(Map.put(@valid_remote, field, string))
|
||||||
|
@ -1237,6 +1240,109 @@ test "follower count is updated when a follower is blocked" do
|
||||||
assert Map.get(user_show, "followers_count") == 2
|
assert Map.get(user_show, "followers_count") == 2
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "list_inactive_users_query/1" do
|
||||||
|
defp days_ago(days) do
|
||||||
|
NaiveDateTime.add(
|
||||||
|
NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second),
|
||||||
|
-days * 60 * 60 * 24,
|
||||||
|
:second
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Users are inactive by default" do
|
||||||
|
total = 10
|
||||||
|
|
||||||
|
users =
|
||||||
|
Enum.map(1..total, fn _ ->
|
||||||
|
insert(:user, last_digest_emailed_at: days_ago(20), info: %{deactivated: false})
|
||||||
|
end)
|
||||||
|
|
||||||
|
inactive_users_ids =
|
||||||
|
Pleroma.User.list_inactive_users_query()
|
||||||
|
|> Pleroma.Repo.all()
|
||||||
|
|> Enum.map(& &1.id)
|
||||||
|
|
||||||
|
Enum.each(users, fn user ->
|
||||||
|
assert user.id in inactive_users_ids
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Only includes users who has no recent activity" do
|
||||||
|
total = 10
|
||||||
|
|
||||||
|
users =
|
||||||
|
Enum.map(1..total, fn _ ->
|
||||||
|
insert(:user, last_digest_emailed_at: days_ago(20), info: %{deactivated: false})
|
||||||
|
end)
|
||||||
|
|
||||||
|
{inactive, active} = Enum.split(users, trunc(total / 2))
|
||||||
|
|
||||||
|
Enum.map(active, fn user ->
|
||||||
|
to = Enum.random(users -- [user])
|
||||||
|
|
||||||
|
{:ok, _} =
|
||||||
|
Pleroma.Web.TwitterAPI.TwitterAPI.create_status(user, %{
|
||||||
|
"status" => "hey @#{to.nickname}"
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
inactive_users_ids =
|
||||||
|
Pleroma.User.list_inactive_users_query()
|
||||||
|
|> Pleroma.Repo.all()
|
||||||
|
|> Enum.map(& &1.id)
|
||||||
|
|
||||||
|
Enum.each(active, fn user ->
|
||||||
|
refute user.id in inactive_users_ids
|
||||||
|
end)
|
||||||
|
|
||||||
|
Enum.each(inactive, fn user ->
|
||||||
|
assert user.id in inactive_users_ids
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Only includes users with no read notifications" do
|
||||||
|
total = 10
|
||||||
|
|
||||||
|
users =
|
||||||
|
Enum.map(1..total, fn _ ->
|
||||||
|
insert(:user, last_digest_emailed_at: days_ago(20), info: %{deactivated: false})
|
||||||
|
end)
|
||||||
|
|
||||||
|
[sender | recipients] = users
|
||||||
|
{inactive, active} = Enum.split(recipients, trunc(total / 2))
|
||||||
|
|
||||||
|
Enum.each(recipients, fn to ->
|
||||||
|
{:ok, _} =
|
||||||
|
Pleroma.Web.TwitterAPI.TwitterAPI.create_status(sender, %{
|
||||||
|
"status" => "hey @#{to.nickname}"
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, _} =
|
||||||
|
Pleroma.Web.TwitterAPI.TwitterAPI.create_status(sender, %{
|
||||||
|
"status" => "hey again @#{to.nickname}"
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
Enum.each(active, fn user ->
|
||||||
|
[n1, _n2] = Pleroma.Notification.for_user(user)
|
||||||
|
{:ok, _} = Pleroma.Notification.read_one(user, n1.id)
|
||||||
|
end)
|
||||||
|
|
||||||
|
inactive_users_ids =
|
||||||
|
Pleroma.User.list_inactive_users_query()
|
||||||
|
|> Pleroma.Repo.all()
|
||||||
|
|> Enum.map(& &1.id)
|
||||||
|
|
||||||
|
Enum.each(active, fn user ->
|
||||||
|
refute user.id in inactive_users_ids
|
||||||
|
end)
|
||||||
|
|
||||||
|
Enum.each(inactive, fn user ->
|
||||||
|
assert user.id in inactive_users_ids
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "toggle_confirmation/1" do
|
describe "toggle_confirmation/1" do
|
||||||
test "if user is confirmed" do
|
test "if user is confirmed" do
|
||||||
user = insert(:user, info: %{confirmation_pending: false})
|
user = insert(:user, info: %{confirmation_pending: false})
|
||||||
|
|
|
@ -4,8 +4,8 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do
|
||||||
|
|
||||||
test "subdomains_regex/1" do
|
test "subdomains_regex/1" do
|
||||||
assert MRF.subdomains_regex(["unsafe.tld", "*.unsafe.tld"]) == [
|
assert MRF.subdomains_regex(["unsafe.tld", "*.unsafe.tld"]) == [
|
||||||
~r/^unsafe.tld$/,
|
~r/^unsafe.tld$/i,
|
||||||
~r/^(.*\.)*unsafe.tld$/
|
~r/^(.*\.)*unsafe.tld$/i
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ test "subdomains_regex/1" do
|
||||||
test "common domains" do
|
test "common domains" do
|
||||||
regexes = MRF.subdomains_regex(["unsafe.tld", "unsafe2.tld"])
|
regexes = MRF.subdomains_regex(["unsafe.tld", "unsafe2.tld"])
|
||||||
|
|
||||||
assert regexes == [~r/^unsafe.tld$/, ~r/^unsafe2.tld$/]
|
assert regexes == [~r/^unsafe.tld$/i, ~r/^unsafe2.tld$/i]
|
||||||
|
|
||||||
assert MRF.subdomain_match?(regexes, "unsafe.tld")
|
assert MRF.subdomain_match?(regexes, "unsafe.tld")
|
||||||
assert MRF.subdomain_match?(regexes, "unsafe2.tld")
|
assert MRF.subdomain_match?(regexes, "unsafe2.tld")
|
||||||
|
@ -24,7 +24,7 @@ test "common domains" do
|
||||||
test "wildcard domains with one subdomain" do
|
test "wildcard domains with one subdomain" do
|
||||||
regexes = MRF.subdomains_regex(["*.unsafe.tld"])
|
regexes = MRF.subdomains_regex(["*.unsafe.tld"])
|
||||||
|
|
||||||
assert regexes == [~r/^(.*\.)*unsafe.tld$/]
|
assert regexes == [~r/^(.*\.)*unsafe.tld$/i]
|
||||||
|
|
||||||
assert MRF.subdomain_match?(regexes, "unsafe.tld")
|
assert MRF.subdomain_match?(regexes, "unsafe.tld")
|
||||||
assert MRF.subdomain_match?(regexes, "sub.unsafe.tld")
|
assert MRF.subdomain_match?(regexes, "sub.unsafe.tld")
|
||||||
|
@ -35,12 +35,52 @@ test "wildcard domains with one subdomain" do
|
||||||
test "wildcard domains with two subdomains" do
|
test "wildcard domains with two subdomains" do
|
||||||
regexes = MRF.subdomains_regex(["*.unsafe.tld"])
|
regexes = MRF.subdomains_regex(["*.unsafe.tld"])
|
||||||
|
|
||||||
assert regexes == [~r/^(.*\.)*unsafe.tld$/]
|
assert regexes == [~r/^(.*\.)*unsafe.tld$/i]
|
||||||
|
|
||||||
assert MRF.subdomain_match?(regexes, "unsafe.tld")
|
assert MRF.subdomain_match?(regexes, "unsafe.tld")
|
||||||
assert MRF.subdomain_match?(regexes, "sub.sub.unsafe.tld")
|
assert MRF.subdomain_match?(regexes, "sub.sub.unsafe.tld")
|
||||||
refute MRF.subdomain_match?(regexes, "sub.anotherunsafe.tld")
|
refute MRF.subdomain_match?(regexes, "sub.anotherunsafe.tld")
|
||||||
refute MRF.subdomain_match?(regexes, "sub.unsafe.tldanother")
|
refute MRF.subdomain_match?(regexes, "sub.unsafe.tldanother")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "matches are case-insensitive" do
|
||||||
|
regexes = MRF.subdomains_regex(["UnSafe.TLD", "UnSAFE2.Tld"])
|
||||||
|
|
||||||
|
assert regexes == [~r/^UnSafe.TLD$/i, ~r/^UnSAFE2.Tld$/i]
|
||||||
|
|
||||||
|
assert MRF.subdomain_match?(regexes, "UNSAFE.TLD")
|
||||||
|
assert MRF.subdomain_match?(regexes, "UNSAFE2.TLD")
|
||||||
|
assert MRF.subdomain_match?(regexes, "unsafe.tld")
|
||||||
|
assert MRF.subdomain_match?(regexes, "unsafe2.tld")
|
||||||
|
|
||||||
|
refute MRF.subdomain_match?(regexes, "EXAMPLE.COM")
|
||||||
|
refute MRF.subdomain_match?(regexes, "example.com")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "describe/0" do
|
||||||
|
test "it works as expected with noop policy" do
|
||||||
|
expected = %{
|
||||||
|
mrf_policies: ["NoOpPolicy"],
|
||||||
|
exclusions: false
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, ^expected} = MRF.describe()
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it works as expected with mock policy" do
|
||||||
|
config = Pleroma.Config.get([:instance, :rewrite_policy])
|
||||||
|
Pleroma.Config.put([:instance, :rewrite_policy], [MRFModuleMock])
|
||||||
|
|
||||||
|
expected = %{
|
||||||
|
mrf_policies: ["MRFModuleMock"],
|
||||||
|
mrf_module_mock: "some config data",
|
||||||
|
exclusions: false
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, ^expected} = MRF.describe()
|
||||||
|
|
||||||
|
Pleroma.Config.put([:instance, :rewrite_policy], config)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
123
test/web/activity_pub/mrf/vocabulary_policy_test.exs
Normal file
123
test/web/activity_pub/mrf/vocabulary_policy_test.exs
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicyTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
alias Pleroma.Web.ActivityPub.MRF.VocabularyPolicy
|
||||||
|
|
||||||
|
describe "accept" do
|
||||||
|
test "it accepts based on parent activity type" do
|
||||||
|
config = Pleroma.Config.get([:mrf_vocabulary, :accept])
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :accept], ["Like"])
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"type" => "Like",
|
||||||
|
"object" => "whatever"
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, ^message} = VocabularyPolicy.filter(message)
|
||||||
|
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :accept], config)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it accepts based on child object type" do
|
||||||
|
config = Pleroma.Config.get([:mrf_vocabulary, :accept])
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :accept], ["Create", "Note"])
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"type" => "Create",
|
||||||
|
"object" => %{
|
||||||
|
"type" => "Note",
|
||||||
|
"content" => "whatever"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, ^message} = VocabularyPolicy.filter(message)
|
||||||
|
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :accept], config)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it does not accept disallowed child objects" do
|
||||||
|
config = Pleroma.Config.get([:mrf_vocabulary, :accept])
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :accept], ["Create", "Note"])
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"type" => "Create",
|
||||||
|
"object" => %{
|
||||||
|
"type" => "Article",
|
||||||
|
"content" => "whatever"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{:reject, nil} = VocabularyPolicy.filter(message)
|
||||||
|
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :accept], config)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it does not accept disallowed parent types" do
|
||||||
|
config = Pleroma.Config.get([:mrf_vocabulary, :accept])
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :accept], ["Announce", "Note"])
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"type" => "Create",
|
||||||
|
"object" => %{
|
||||||
|
"type" => "Note",
|
||||||
|
"content" => "whatever"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{:reject, nil} = VocabularyPolicy.filter(message)
|
||||||
|
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :accept], config)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "reject" do
|
||||||
|
test "it rejects based on parent activity type" do
|
||||||
|
config = Pleroma.Config.get([:mrf_vocabulary, :reject])
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :reject], ["Like"])
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"type" => "Like",
|
||||||
|
"object" => "whatever"
|
||||||
|
}
|
||||||
|
|
||||||
|
{:reject, nil} = VocabularyPolicy.filter(message)
|
||||||
|
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :reject], config)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it rejects based on child object type" do
|
||||||
|
config = Pleroma.Config.get([:mrf_vocabulary, :reject])
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :reject], ["Note"])
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"type" => "Create",
|
||||||
|
"object" => %{
|
||||||
|
"type" => "Note",
|
||||||
|
"content" => "whatever"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{:reject, nil} = VocabularyPolicy.filter(message)
|
||||||
|
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :reject], config)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it passes through objects that aren't disallowed" do
|
||||||
|
config = Pleroma.Config.get([:mrf_vocabulary, :reject])
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :reject], ["Like"])
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"type" => "Announce",
|
||||||
|
"object" => "whatever"
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, ^message} = VocabularyPolicy.filter(message)
|
||||||
|
|
||||||
|
Pleroma.Config.put([:mrf_vocabulary, :reject], config)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,11 +5,71 @@
|
||||||
defmodule Pleroma.Web.ActivityPub.RelayTest do
|
defmodule Pleroma.Web.ActivityPub.RelayTest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Relay
|
alias Pleroma.Web.ActivityPub.Relay
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
test "gets an actor for the relay" do
|
test "gets an actor for the relay" do
|
||||||
user = Relay.get_actor()
|
user = Relay.get_actor()
|
||||||
|
assert user.ap_id == "#{Pleroma.Web.Endpoint.url()}/relay"
|
||||||
|
end
|
||||||
|
|
||||||
assert user.ap_id =~ "/relay"
|
describe "follow/1" do
|
||||||
|
test "returns errors when user not found" do
|
||||||
|
assert Relay.follow("test-ap-id") == {:error, "Could not fetch by AP id"}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns activity" do
|
||||||
|
user = insert(:user)
|
||||||
|
service_actor = Relay.get_actor()
|
||||||
|
assert {:ok, %Activity{} = activity} = Relay.follow(user.ap_id)
|
||||||
|
assert activity.actor == "#{Pleroma.Web.Endpoint.url()}/relay"
|
||||||
|
assert user.ap_id in activity.recipients
|
||||||
|
assert activity.data["type"] == "Follow"
|
||||||
|
assert activity.data["actor"] == service_actor.ap_id
|
||||||
|
assert activity.data["object"] == user.ap_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "unfollow/1" do
|
||||||
|
test "returns errors when user not found" do
|
||||||
|
assert Relay.unfollow("test-ap-id") == {:error, "Could not fetch by AP id"}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns activity" do
|
||||||
|
user = insert(:user)
|
||||||
|
service_actor = Relay.get_actor()
|
||||||
|
ActivityPub.follow(service_actor, user)
|
||||||
|
assert {:ok, %Activity{} = activity} = Relay.unfollow(user.ap_id)
|
||||||
|
assert activity.actor == "#{Pleroma.Web.Endpoint.url()}/relay"
|
||||||
|
assert user.ap_id in activity.recipients
|
||||||
|
assert activity.data["type"] == "Undo"
|
||||||
|
assert activity.data["actor"] == service_actor.ap_id
|
||||||
|
assert activity.data["to"] == [user.ap_id]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "publish/1" do
|
||||||
|
test "returns error when activity not `Create` type" do
|
||||||
|
activity = insert(:like_activity)
|
||||||
|
assert Relay.publish(activity) == {:error, "Not implemented"}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns error when activity not public" do
|
||||||
|
activity = insert(:direct_note_activity)
|
||||||
|
assert Relay.publish(activity) == {:error, false}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns announce activity" do
|
||||||
|
service_actor = Relay.get_actor()
|
||||||
|
note = insert(:note_activity)
|
||||||
|
assert {:ok, %Activity{} = activity, %Object{} = obj} = Relay.publish(note)
|
||||||
|
assert activity.data["type"] == "Announce"
|
||||||
|
assert activity.data["actor"] == service_actor.ap_id
|
||||||
|
assert activity.data["object"] == obj.data["id"]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue