Merge branch 'develop' into issue/1276
This commit is contained in:
commit
10f452ad1f
|
@ -0,0 +1,2 @@
|
|||
*.ex diff=elixir
|
||||
*.exs diff=elixir
|
|
@ -73,7 +73,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- User notification settings: Add `privacy_option` option.
|
||||
- Support for custom Elixir modules (such as MRF policies)
|
||||
- User settings: Add _This account is a_ option.
|
||||
- A new users admin digest email
|
||||
- OAuth: admin scopes support (relevant setting: `[:auth, :enforce_oauth_admin_scope_usage]`).
|
||||
- Add an option `authorized_fetch_mode` to require HTTP signatures for AP fetches.
|
||||
- ActivityPub: support for `replies` collection (output for outgoing federation & fetching on incoming federation).
|
||||
- Mix task to refresh counter cache (`mix pleroma.refresh_counter_cache`)
|
||||
<details>
|
||||
<summary>API Changes</summary>
|
||||
|
||||
|
@ -115,6 +119,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Configuration: `feed.logo` option for tag feed.
|
||||
- Tag feed: `/tags/:tag.rss` - list public statuses by hashtag.
|
||||
- Mastodon API: Add `reacted` property to `emoji_reactions`
|
||||
- Pleroma API: Add reactions for a single emoji.
|
||||
- ActivityPub: `[:activitypub, :note_replies_output_limit]` setting sets the number of note self-replies to output on outgoing federation.
|
||||
- Admin API: `GET /api/pleroma/admin/stats` to get status count by visibility scope
|
||||
</details>
|
||||
|
||||
### Fixed
|
||||
|
|
|
@ -219,6 +219,8 @@
|
|||
max_expiration: 365 * 24 * 60 * 60
|
||||
},
|
||||
registrations_open: true,
|
||||
invites_enabled: false,
|
||||
account_activation_required: false,
|
||||
federating: true,
|
||||
federation_incoming_replies_max_depth: 100,
|
||||
federation_reachability_timeout_days: 7,
|
||||
|
@ -326,7 +328,9 @@
|
|||
unfollow_blocked: true,
|
||||
outgoing_blocks: true,
|
||||
follow_handshake_timeout: 500,
|
||||
sign_object_fetches: true
|
||||
note_replies_output_limit: 5,
|
||||
sign_object_fetches: true,
|
||||
authorized_fetch_mode: false
|
||||
|
||||
config :pleroma, :streamer,
|
||||
workers: 3,
|
||||
|
@ -480,13 +484,16 @@
|
|||
transmogrifier: 20,
|
||||
scheduled_activities: 10,
|
||||
background: 5,
|
||||
attachments_cleanup: 5
|
||||
remote_fetcher: 2,
|
||||
attachments_cleanup: 5,
|
||||
new_users_digest: 1
|
||||
],
|
||||
crontab: [
|
||||
{"0 0 * * *", Pleroma.Workers.Cron.ClearOauthTokenWorker},
|
||||
{"0 * * * *", Pleroma.Workers.Cron.StatsWorker},
|
||||
{"* * * * *", Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker},
|
||||
{"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker}
|
||||
{"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
|
||||
{"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}
|
||||
]
|
||||
|
||||
config :pleroma, :workers,
|
||||
|
@ -560,6 +567,8 @@
|
|||
text_muted_color: "#b9b9ba"
|
||||
}
|
||||
|
||||
config :pleroma, Pleroma.Emails.NewUsersDigestEmail, enabled: false
|
||||
|
||||
config :prometheus, Pleroma.Web.Endpoint.MetricsExporter, path: "/api/pleroma/app_metrics"
|
||||
|
||||
config :pleroma, Pleroma.ScheduledActivity,
|
||||
|
@ -612,6 +621,8 @@
|
|||
|
||||
config :pleroma, configurable_from_database: false
|
||||
|
||||
config :pleroma, Pleroma.Repo, parameters: [gin_fuzzy_search_limit: "500"]
|
||||
|
||||
# Import environment specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
import_config "#{Mix.env()}.exs"
|
||||
|
|
|
@ -101,7 +101,7 @@
|
|||
%{
|
||||
key: :versions,
|
||||
type: {:list, :atom},
|
||||
description: "List of TLS version to use",
|
||||
description: "List of TLS versions to use",
|
||||
suggestions: [:tlsv1, ":tlsv1.1", ":tlsv1.2"]
|
||||
}
|
||||
]
|
||||
|
@ -534,7 +534,8 @@
|
|||
%{
|
||||
key: :description,
|
||||
type: :string,
|
||||
description: "The instance's description, can be seen in nodeinfo and /api/v1/instance",
|
||||
description:
|
||||
"The instance's description. It can be seen in nodeinfo and `/api/v1/instance`",
|
||||
suggestions: [
|
||||
"Very cool instance"
|
||||
]
|
||||
|
@ -661,7 +662,7 @@
|
|||
label: "Fed. incoming replies max depth",
|
||||
type: :integer,
|
||||
description:
|
||||
"Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while" <>
|
||||
"Max. depth of reply-to and reply activities fetching on incoming federation, to prevent out-of-memory situations while" <>
|
||||
" fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes.",
|
||||
suggestions: [
|
||||
100
|
||||
|
@ -770,7 +771,7 @@
|
|||
key: :cleanup_attachments,
|
||||
type: :boolean,
|
||||
description: """
|
||||
"Enable to remove associated attachments when status is removed.
|
||||
Enable to remove associated attachments when status is removed.
|
||||
This will not affect duplicates and attachments without status.
|
||||
Enabling this will increase load to database when deleting statuses on larger instances.
|
||||
"""
|
||||
|
@ -838,7 +839,7 @@
|
|||
%{
|
||||
key: :healthcheck,
|
||||
type: :boolean,
|
||||
description: "If enabled, system data will be shown on /api/pleroma/healthcheck"
|
||||
description: "If enabled, system data will be shown on `/api/pleroma/healthcheck`"
|
||||
},
|
||||
%{
|
||||
key: :remote_post_retention_days,
|
||||
|
@ -1296,14 +1297,14 @@
|
|||
%{
|
||||
key: :media_removal,
|
||||
type: {:list, :string},
|
||||
description: "List of instances to remove medias from",
|
||||
description: "List of instances to strip media attachments from",
|
||||
suggestions: ["example.com", "*.example.com"]
|
||||
},
|
||||
%{
|
||||
key: :media_nsfw,
|
||||
label: "Media NSFW",
|
||||
type: {:list, :string},
|
||||
description: "List of instances to put medias as NSFW (sensitive) from",
|
||||
description: "List of instances to tag all media as NSFW (sensitive) from",
|
||||
suggestions: ["example.com", "*.example.com"]
|
||||
},
|
||||
%{
|
||||
|
@ -1422,21 +1423,21 @@
|
|||
key: :reject,
|
||||
type: [:string, :regex],
|
||||
description:
|
||||
"A list of patterns which result in message being rejected, each pattern can be a string or a regular expression.",
|
||||
"A list of patterns which result in message being rejected. Each pattern can be a string or a regular expression.",
|
||||
suggestions: ["foo", ~r/foo/iu]
|
||||
},
|
||||
%{
|
||||
key: :federated_timeline_removal,
|
||||
type: [:string, :regex],
|
||||
description:
|
||||
"A list of patterns which result in message being removed from federated timelines (a.k.a unlisted), each pattern can be a string or a regular expression.",
|
||||
"A list of patterns which result in message being removed from federated timelines (a.k.a unlisted). Each pattern can be a string or a regular expression.",
|
||||
suggestions: ["foo", ~r/foo/iu]
|
||||
},
|
||||
%{
|
||||
key: :replace,
|
||||
type: [{:tuple, :string, :string}, {:tuple, :regex, :string}],
|
||||
description:
|
||||
"A list of tuples containing {pattern, replacement}, pattern can be a string or a regular expression.",
|
||||
"A list of tuples containing {pattern, replacement}. Each pattern can be a string or a regular expression.",
|
||||
suggestions: [{"foo", "bar"}, {~r/foo/iu, "bar"}]
|
||||
}
|
||||
]
|
||||
|
@ -1451,7 +1452,7 @@
|
|||
%{
|
||||
key: :actors,
|
||||
type: {:list, :string},
|
||||
description: "A list of actors, for which to drop any posts mentioning",
|
||||
description: "A list of actors for which any post mentioning them will be dropped.",
|
||||
suggestions: ["actor1", "actor2"]
|
||||
}
|
||||
]
|
||||
|
@ -1789,6 +1790,12 @@
|
|||
type: :boolean,
|
||||
description: "Sign object fetches with HTTP signatures"
|
||||
},
|
||||
%{
|
||||
key: :note_replies_output_limit,
|
||||
type: :integer,
|
||||
description:
|
||||
"The number of Note replies' URIs to be included with outgoing federation (`5` to match Mastodon hardcoded value, `0` to disable the output)."
|
||||
},
|
||||
%{
|
||||
key: :follow_handshake_timeout,
|
||||
type: :integer,
|
||||
|
@ -1855,9 +1862,8 @@
|
|||
type: :string,
|
||||
description:
|
||||
"A mailto link for the administrative contact." <>
|
||||
" It's best if this email is not a personal email address, but rather a group email so that if a person leaves an organization," <>
|
||||
" is unavailable for an extended period, or otherwise can't respond, someone else on the list can.",
|
||||
suggestions: ["Subject"]
|
||||
" It's best if this email is not a personal email address, but rather a group email to the instance moderation team.",
|
||||
suggestions: ["mailto:moderators@pleroma.com"]
|
||||
},
|
||||
%{
|
||||
key: :public_key,
|
||||
|
@ -1924,7 +1930,7 @@
|
|||
key: :admin_token,
|
||||
type: :string,
|
||||
description: "Token",
|
||||
suggestions: ["some_random_token"]
|
||||
suggestions: ["We recommend a secure random string or UUID"]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -1986,6 +1992,7 @@
|
|||
"Background jobs queues (keys: queues, values: max numbers of concurrent jobs)",
|
||||
suggestions: [
|
||||
activity_expiration: 10,
|
||||
attachments_cleanup: 5,
|
||||
background: 5,
|
||||
federator_incoming: 50,
|
||||
federator_outgoing: 50,
|
||||
|
@ -2001,6 +2008,12 @@
|
|||
description: "Activity expiration queue",
|
||||
suggestions: [10]
|
||||
},
|
||||
%{
|
||||
key: :attachments_cleanup,
|
||||
type: :integer,
|
||||
description: "Attachment deletion queue",
|
||||
suggestions: [5]
|
||||
},
|
||||
%{
|
||||
key: :background,
|
||||
type: :integer,
|
||||
|
@ -2099,7 +2112,7 @@
|
|||
%{
|
||||
key: :enabled,
|
||||
type: :boolean,
|
||||
description: "Enables/disables RichMedia."
|
||||
description: "Enables RichMedia parsing of URLs."
|
||||
},
|
||||
%{
|
||||
key: :ignore_hosts,
|
||||
|
@ -2145,8 +2158,7 @@
|
|||
%{
|
||||
key: :enabled,
|
||||
type: :boolean,
|
||||
description:
|
||||
"If enabled, when a new user is federated with, fetch some of their latest posts"
|
||||
description: "Fetch posts when a new user is federated with"
|
||||
},
|
||||
%{
|
||||
key: :pages,
|
||||
|
@ -2165,13 +2177,13 @@
|
|||
%{
|
||||
key: :class,
|
||||
type: [:string, false],
|
||||
description: "Specify the class to be added to the generated link. `False` to clear",
|
||||
description: "Specify the class to be added to the generated link. Disable to clear",
|
||||
suggestions: ["auto-linker", false]
|
||||
},
|
||||
%{
|
||||
key: :rel,
|
||||
type: [:string, false],
|
||||
description: "Override the rel attribute. `False` to clear",
|
||||
description: "Override the rel attribute. Disable to clear",
|
||||
suggestions: ["ugc", "noopener noreferrer", false]
|
||||
},
|
||||
%{
|
||||
|
@ -2281,7 +2293,7 @@
|
|||
key: :ssl,
|
||||
label: "SSL",
|
||||
type: :boolean,
|
||||
description: "`True` to use SSL, usually implies the port 636"
|
||||
description: "Enable to use SSL, usually implies the port 636"
|
||||
},
|
||||
%{
|
||||
key: :sslopts,
|
||||
|
@ -2308,7 +2320,7 @@
|
|||
key: :tls,
|
||||
label: "TLS",
|
||||
type: :boolean,
|
||||
description: "`True` to start TLS, usually implies the port 389"
|
||||
description: "Enable to use STARTTLS, usually implies the port 389"
|
||||
},
|
||||
%{
|
||||
key: :tlsopts,
|
||||
|
@ -2358,7 +2370,7 @@
|
|||
description:
|
||||
"OAuth admin scope requirement toggle. " <>
|
||||
"If enabled, admin actions explicitly demand admin OAuth scope(s) presence in OAuth token " <>
|
||||
"(client app must support admin scopes). If `false` and token doesn't have admin scope(s)," <>
|
||||
"(client app must support admin scopes). If disabled and token doesn't have admin scope(s)," <>
|
||||
"`is_admin` user flag grants access to admin-specific actions."
|
||||
},
|
||||
%{
|
||||
|
@ -2380,7 +2392,7 @@
|
|||
key: :oauth_consumer_strategies,
|
||||
type: {:list, :string},
|
||||
description:
|
||||
"The list of enabled OAuth consumer strategies; by default it's set by OAUTH_CONSUMER_STRATEGIES environment variable." <>
|
||||
"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>).",
|
||||
suggestions: ["twitter", "keycloak:ueberauth_keycloak_strategy"]
|
||||
|
@ -2496,6 +2508,20 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
group: :pleroma,
|
||||
key: Pleroma.Emails.NewUsersDigestEmail,
|
||||
type: :group,
|
||||
description: "New users admin email digest",
|
||||
children: [
|
||||
%{
|
||||
key: :enabled,
|
||||
type: :boolean,
|
||||
description: "enables new users admin digest email when `true`",
|
||||
suggestions: [false]
|
||||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
group: :pleroma,
|
||||
key: :oauth2,
|
||||
|
@ -2517,7 +2543,7 @@
|
|||
%{
|
||||
key: :clean_expired_tokens,
|
||||
type: :boolean,
|
||||
description: "Enable a background job to clean expired oauth tokens. Default: `false`."
|
||||
description: "Enable a background job to clean expired oauth tokens. Default: disabled."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -2577,7 +2603,7 @@
|
|||
%{
|
||||
key: :rum_enabled,
|
||||
type: :boolean,
|
||||
description: "If RUM indexes should be used. Default: `false`"
|
||||
description: "If RUM indexes should be used. Default: disabled"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -2963,7 +2989,7 @@
|
|||
%{
|
||||
key: :enabled,
|
||||
type: :boolean,
|
||||
description: "Enable/disable the plug. Default: `false`."
|
||||
description: "Enable/disable the plug. Default: disabled."
|
||||
},
|
||||
%{
|
||||
key: :headers,
|
||||
|
@ -3017,7 +3043,7 @@
|
|||
%{
|
||||
key: :enabled,
|
||||
type: :boolean,
|
||||
description: "Enables the rendering of static HTML. Defaults to `false`."
|
||||
description: "Enables the rendering of static HTML. Default: disabled."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -3093,7 +3119,7 @@
|
|||
key: :configurable_from_database,
|
||||
type: :boolean,
|
||||
description:
|
||||
"Allow transferring configuration to DB with the subsequent customization from Admin api. Defaults to `false`"
|
||||
"Allow transferring configuration to DB with the subsequent customization from Admin api. Default: disabled"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -94,6 +94,8 @@
|
|||
|
||||
config :pleroma, :modules, runtime_dir: "test/fixtures/modules"
|
||||
|
||||
config :pleroma, Pleroma.Emails.NewUsersDigestEmail, enabled: true
|
||||
|
||||
if File.exists?("./config/test.secret.exs") do
|
||||
import_config "test.secret.exs"
|
||||
else
|
||||
|
|
|
@ -682,6 +682,8 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
|||
|
||||
### Get list of merged default settings with saved in database.
|
||||
|
||||
*If `need_reboot` flag exists in response, instance must be restarted, so reboot time settings can take effect.*
|
||||
|
||||
**Only works when configuration from database is enabled.**
|
||||
|
||||
- Params:
|
||||
|
@ -692,20 +694,24 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
|||
|
||||
```json
|
||||
{
|
||||
configs: [
|
||||
"configs": [
|
||||
{
|
||||
"group": ":pleroma",
|
||||
"key": "Pleroma.Upload",
|
||||
"value": []
|
||||
}
|
||||
]
|
||||
],
|
||||
"need_reboot": true
|
||||
}
|
||||
```
|
||||
need_reboot - *optional*, if were changed reboot time settings.
|
||||
|
||||
## `POST /api/pleroma/admin/config`
|
||||
|
||||
### Update config settings
|
||||
|
||||
*If `need_reboot` flag exists in response, instance must be restarted, so reboot time settings can take effect.*
|
||||
|
||||
**Only works when configuration from database is enabled.**
|
||||
|
||||
Some modifications are necessary to save the config settings correctly:
|
||||
|
@ -793,7 +799,7 @@ config :quack,
|
|||
```
|
||||
```json
|
||||
{
|
||||
configs: [
|
||||
"configs": [
|
||||
{"group": ":quack", "key": ":level", "value": ":debug"},
|
||||
{"group": ":quack", "key": ":meta", "value": [":all"]},
|
||||
...
|
||||
|
@ -804,7 +810,7 @@ config :quack,
|
|||
|
||||
```json
|
||||
{
|
||||
configs: [
|
||||
"configs": [
|
||||
{
|
||||
"group": ":pleroma",
|
||||
"key": "Pleroma.Upload",
|
||||
|
@ -836,15 +842,17 @@ config :quack,
|
|||
- 400 Bad Request `"To use this endpoint you need to enable configuration from database."`
|
||||
```json
|
||||
{
|
||||
configs: [
|
||||
"configs": [
|
||||
{
|
||||
"group": ":pleroma",
|
||||
"key": "Pleroma.Upload",
|
||||
"value": [...]
|
||||
}
|
||||
]
|
||||
],
|
||||
"need_reboot": true
|
||||
}
|
||||
```
|
||||
need_reboot - *optional*, if were changed reboot time settings.
|
||||
|
||||
## ` GET /api/pleroma/admin/config/descriptions`
|
||||
|
||||
|
@ -931,3 +939,20 @@ Loads json generated from `config/descriptions.exs`.
|
|||
- Params:
|
||||
- `nicknames`
|
||||
- Response: Array of user nicknames
|
||||
|
||||
## `GET /api/pleroma/admin/stats`
|
||||
|
||||
### Stats
|
||||
|
||||
- Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"status_visibility": {
|
||||
"direct": 739,
|
||||
"private": 9,
|
||||
"public": 17,
|
||||
"unlisted": 14
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
@ -459,3 +459,16 @@ Emoji reactions work a lot like favourites do. They make it possible to react to
|
|||
{"name": "☕", "count": 1, "me": false, "accounts": [{"id" => "abc..."}]}
|
||||
]
|
||||
```
|
||||
|
||||
## `GET /api/v1/pleroma/statuses/:id/reactions/:emoji`
|
||||
### Get an object of emoji to account mappings with accounts that reacted to the post for a specific emoji`
|
||||
* Method: `GET`
|
||||
* Authentication: optional
|
||||
* Params: None
|
||||
* Response: JSON, a list of emoji/account list tuples
|
||||
* Example Response:
|
||||
```json
|
||||
[
|
||||
{"name": "😀", "count": 2, "me": true, "accounts": [{"id" => "xyz.."...}, {"id" => "zyx..."}]}
|
||||
]
|
||||
```
|
||||
|
|
|
@ -18,7 +18,9 @@
|
|||
6. Run `sudo -Hu postgres pg_restore -d <pleroma_db> -v -1 </path/to/backup_location/pleroma.pgdump>`
|
||||
7. If you installed a newer Pleroma version, you should run `mix ecto.migrate`[^1]. This task performs database migrations, if there were any.
|
||||
8. Restart the Pleroma service.
|
||||
|
||||
9. After you've restarted Pleroma, you will notice that postgres will take up more cpu resources than usual. A lot in fact. To fix this you must do a VACUUM ANLAYZE. This can also be done while the instance is still running like so:
|
||||
$ sudo -u postgres psql pleroma_database_name
|
||||
pleroma=# VACUUM ANALYZE;
|
||||
[^1]: Prefix with `MIX_ENV=prod` to run it using the production config file.
|
||||
|
||||
## Remove
|
||||
|
|
|
@ -1,4 +1,21 @@
|
|||
# Updating your instance
|
||||
|
||||
You should **always check the release notes/changelog** in case there are config deprecations, special update special update steps, etc.
|
||||
|
||||
Besides that, doing the following is generally enough:
|
||||
|
||||
## For OTP installations
|
||||
|
||||
```sh
|
||||
# Download the new release
|
||||
su pleroma -s $SHELL -lc "./bin/pleroma_ctl update"
|
||||
|
||||
# Migrate the database, you are advised to stop the instance before doing that
|
||||
su pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate"
|
||||
```
|
||||
|
||||
## For from source installations (using git)
|
||||
|
||||
1. Go to the working directory of Pleroma (default is `/opt/pleroma`)
|
||||
2. Run `git pull`. This pulls the latest changes from upstream.
|
||||
3. Run `mix deps.get`. This pulls in any new dependencies.
|
||||
|
|
|
@ -143,10 +143,11 @@ config :pleroma, :mrf_user_allowlist,
|
|||
* `:reject` rejects the message entirely
|
||||
|
||||
### :activitypub
|
||||
* ``unfollow_blocked``: Whether blocks result in people getting unfollowed
|
||||
* ``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
|
||||
* ``sign_object_fetches``: Sign object fetches with HTTP signatures
|
||||
* `unfollow_blocked`: Whether blocks result in people getting unfollowed
|
||||
* `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
|
||||
* `sign_object_fetches`: Sign object fetches with HTTP signatures
|
||||
* `authorized_fetch_mode`: Require HTTP signatures for AP fetches
|
||||
|
||||
### :fetch_initial_posts
|
||||
* `enabled`: if enabled, when a new user is federated with, fetch some of their latest posts
|
||||
|
@ -501,6 +502,10 @@ Email notifications settings.
|
|||
- `:logo` - a path to a custom logo. Set it to `nil` to use the default Pleroma logo.
|
||||
- `:styling` - a map with color settings for email templates.
|
||||
|
||||
### Pleroma.Emails.NewUsersDigestEmail
|
||||
|
||||
- `:enabled` - a boolean, enables new users admin digest email when `true`. Defaults to `false`.
|
||||
|
||||
## Background jobs
|
||||
|
||||
### Oban
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
# Theming your instance
|
||||
|
||||
To add a custom theme to your instance, you'll first need to get a custom theme, upload it to the server, make it available to the instance and eventually you can set it as default.
|
||||
|
||||
## Getting a custom theme
|
||||
|
||||
### Create your own theme
|
||||
|
||||
* You can create your own theme using the Pleroma FE by going to settings (gear on the top right) and choose the Theme tab. Here you have the options to create a personal theme.
|
||||
* To download your theme, you can do Save preset
|
||||
* If you want to upload a theme to customise it further, you can upload it using Load preset
|
||||
|
||||
This will only save the theme for you personally. To make it available to the whole instance, you'll need to upload it to the server.
|
||||
|
||||
### Get an existing theme
|
||||
|
||||
* You can download a theme from another instance by going to that instance, go to settings and make sure you have the theme selected that you want. Then you can do Save preset to download it.
|
||||
* You can also find and download custom themes at <https://plthemes.vulpes.one/>
|
||||
|
||||
## Adding the custom theme to the instance
|
||||
|
||||
### Upload the theme to the server
|
||||
|
||||
Themes can be found in the [static directory](static_dir.md). Create `STATIC-DIR/static/themes/` if needed and copy your theme there. Next you need to add an entry for your theme to `STATIC-DIR/static/styles.json`. If you use a from source installation, you'll first need to copy the file from `priv/static/static/styles.json`.
|
||||
|
||||
Example of `styles.json` where we add our own `my-awesome-theme.json`
|
||||
```json
|
||||
{
|
||||
"pleroma-dark": [ "Pleroma Dark", "#121a24", "#182230", "#b9b9ba", "#d8a070", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
|
||||
"pleroma-light": [ "Pleroma Light", "#f2f4f6", "#dbe0e8", "#304055", "#f86f0f", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
|
||||
"classic-dark": [ "Classic Dark", "#161c20", "#282e32", "#b9b9b9", "#baaa9c", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
|
||||
"bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"],
|
||||
"ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ],
|
||||
"monokai": [ "Monokai", "#272822", "#383830", "#f8f8f2", "#f92672", "#F92672", "#a6e22e", "#66d9ef", "#f4bf75" ],
|
||||
|
||||
"redmond-xx": "/static/themes/redmond-xx.json",
|
||||
"redmond-xx-se": "/static/themes/redmond-xx-se.json",
|
||||
"redmond-xxi": "/static/themes/redmond-xxi.json",
|
||||
"breezy-dark": "/static/themes/breezy-dark.json",
|
||||
"breezy-light": "/static/themes/breezy-light.json",
|
||||
"mammal": "/static/themes/mammal.json",
|
||||
"my-awesome-theme": "/static/themes/my-awesome-theme.json"
|
||||
}
|
||||
```
|
||||
|
||||
Now you'll already be able to select the theme in Pleroma FE from the drop-down. You don't need to restart Pleroma because we only changed static served files. You may need to refresh the page in your browser. You'll notice however that the theme doesn't have a name, it's just an empty entry in the drop-down.
|
||||
|
||||
### Give the theme a name
|
||||
|
||||
When you open one of the themes that ship with Pleroma, you'll notice that the json has a `"name"` key. Add a key-value pair to your theme where the key name is `"name"` and the value the name you want to give your theme. After this you can refresh te page in your browser and the name should be visible in the drop-down.
|
||||
|
||||
Example of `my-awesome-theme.json` where we add the name "My Awesome Theme"
|
||||
```json
|
||||
{
|
||||
"_pleroma_theme_version": 2,
|
||||
"name": "My Awesome Theme",
|
||||
"theme": {}
|
||||
}
|
||||
```
|
||||
|
||||
### Set as default theme
|
||||
|
||||
Now we can set the new theme as default in the [Pleroma FE configuration](General-tips-for-customizing-Pleroma-FE.md).
|
||||
|
||||
Example of adding the new theme in the back-end config files
|
||||
```elixir
|
||||
config :pleroma, :frontend_configurations,
|
||||
pleroma_fe: %{
|
||||
theme: "my-awesome-theme"
|
||||
}
|
||||
```
|
||||
|
||||
If you added it in the back-end configuration file, you'll need to restart your instance for the changes to take effect. If you don't see the changes, it's probably because the browser has cached the previous theme. In that case you'll want to clear browser caches. Alternatively you can use a private/incognito window just to see the changes.
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
# Message Rewrite Facility
|
||||
|
||||
The Message Rewrite Facility (MRF) is a subsystem that is implemented as a series of hooks that allows the administrator to rewrite or discard messages.
|
||||
|
||||
Possible uses include:
|
||||
|
@ -10,7 +11,8 @@ Possible uses include:
|
|||
* removing media from messages
|
||||
* sending only public messages to a specific instance
|
||||
|
||||
The MRF provides user-configurable policies. The default policy is `NoOpPolicy`, which disables the MRF functionality. Pleroma also includes an easy to use policy called `SimplePolicy` which maps messages matching certain pre-defined criterion to actions built into the policy module.
|
||||
The MRF provides user-configurable policies. The default policy is `NoOpPolicy`, which disables the MRF functionality. Pleroma also includes an easy to use policy called `SimplePolicy` which maps messages matching certain pre-defined criterion to actions built into the policy module.
|
||||
|
||||
It is possible to use multiple, active MRF policies at the same time.
|
||||
|
||||
## Quarantine Instances
|
||||
|
@ -18,7 +20,8 @@ It is possible to use multiple, active MRF policies at the same time.
|
|||
You have the ability to prevent from private / followers-only messages from federating with specific instances. Which means they will only get the public or unlisted messages from your instance.
|
||||
|
||||
If, for example, you're using `MIX_ENV=prod` aka using production mode, you would open your configuration file located in `config/prod.secret.exs` and edit or add the option under your `:instance` config object. Then you would specify the instance within quotes.
|
||||
```
|
||||
|
||||
```elixir
|
||||
config :pleroma, :instance,
|
||||
[...]
|
||||
quarantined_instances: ["instance.example", "other.example"]
|
||||
|
@ -28,15 +31,15 @@ config :pleroma, :instance,
|
|||
|
||||
`SimplePolicy` is capable of handling most common admin tasks.
|
||||
|
||||
To use `SimplePolicy`, you must enable it. Do so by adding the following to your `:instance` config object, so that it looks like this:
|
||||
To use `SimplePolicy`, you must enable it. Do so by adding the following to your `:instance` config object, so that it looks like this:
|
||||
|
||||
```
|
||||
```elixir
|
||||
config :pleroma, :instance,
|
||||
[...]
|
||||
rewrite_policy: Pleroma.Web.ActivityPub.MRF.SimplePolicy
|
||||
```
|
||||
|
||||
Once `SimplePolicy` is enabled, you can configure various groups in the `:mrf_simple` config object. These groups are:
|
||||
Once `SimplePolicy` is enabled, you can configure various groups in the `:mrf_simple` config object. These groups are:
|
||||
|
||||
* `media_removal`: Servers in this group will have media stripped from incoming messages.
|
||||
* `media_nsfw`: Servers in this group will have the #nsfw tag and sensitive setting injected into incoming messages which contain media.
|
||||
|
@ -50,7 +53,7 @@ Servers should be configured as lists.
|
|||
|
||||
This example will enable `SimplePolicy`, block media from `illegalporn.biz`, mark media as NSFW from `porn.biz` and `porn.business`, reject messages from `spam.com`, remove messages from `spam.university` from the federated timeline and block reports (flags) from `whiny.whiner`:
|
||||
|
||||
```
|
||||
```elixir
|
||||
config :pleroma, :instance,
|
||||
rewrite_policy: [Pleroma.Web.ActivityPub.MRF.SimplePolicy]
|
||||
|
||||
|
@ -60,30 +63,31 @@ config :pleroma, :mrf_simple,
|
|||
reject: ["spam.com"],
|
||||
federated_timeline_removal: ["spam.university"],
|
||||
report_removal: ["whiny.whiner"]
|
||||
|
||||
```
|
||||
|
||||
### Use with Care
|
||||
|
||||
The effects of MRF policies can be very drastic. It is important to use this functionality carefully. Always try to talk to an admin before writing an MRF policy concerning their instance.
|
||||
The effects of MRF policies can be very drastic. It is important to use this functionality carefully. Always try to talk to an admin before writing an MRF policy concerning their instance.
|
||||
|
||||
## Writing your own MRF Policy
|
||||
|
||||
As discussed above, the MRF system is a modular system that supports pluggable policies. This means that an admin may write a custom MRF policy in Elixir or any other language that runs on the Erlang VM, by specifying the module name in the `rewrite_policy` config setting.
|
||||
As discussed above, the MRF system is a modular system that supports pluggable policies. This means that an admin may write a custom MRF policy in Elixir or any other language that runs on the Erlang VM, by specifying the module name in the `rewrite_policy` config setting.
|
||||
|
||||
For example, here is a sample policy module which rewrites all messages to "new message content":
|
||||
|
||||
```elixir
|
||||
# This is a sample MRF policy which rewrites all Notes to have "new message
|
||||
# content."
|
||||
defmodule Site.RewritePolicy do
|
||||
@behavior Pleroma.Web.ActivityPub.MRF
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.RewritePolicy do
|
||||
@moduledoc "MRF policy which rewrites all Notes to have 'new message content'."
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
# Catch messages which contain Note objects with actual data to filter.
|
||||
# Capture the object as `object`, the message content as `content` and the
|
||||
# message itself as `message`.
|
||||
@impl true
|
||||
def filter(%{"type" => Create", "object" => {"type" => "Note", "content" => content} = object} = message)
|
||||
def filter(
|
||||
%{"type" => "Create", "object" => %{"type" => "Note", "content" => content} = object} =
|
||||
message
|
||||
)
|
||||
when is_binary(content) do
|
||||
# Subject / CW is stored as summary instead of `name` like other AS2 objects
|
||||
# because of Mastodon doing it that way.
|
||||
|
@ -106,17 +110,22 @@ defmodule Site.RewritePolicy do
|
|||
# Let all other messages through without modifying them.
|
||||
@impl true
|
||||
def filter(message), do: {:ok, message}
|
||||
|
||||
@impl true
|
||||
def describe do
|
||||
{:ok, %{mrf_sample: %{content: "new message content"}}}`
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
If you save this file as `lib/site/mrf/rewrite_policy.ex`, it will be included when you next rebuild Pleroma. You can enable it in the configuration like so:
|
||||
If you save this file as `lib/pleroma/web/activity_pub/mrf/rewrite_policy.ex`, it will be included when you next rebuild Pleroma. You can enable it in the configuration like so:
|
||||
|
||||
```
|
||||
```elixir
|
||||
config :pleroma, :instance,
|
||||
rewrite_policy: [
|
||||
Pleroma.Web.ActivityPub.MRF.SimplePolicy,
|
||||
Site.RewritePolicy
|
||||
Pleroma.Web.ActivityPub.MRF.RewritePolicy
|
||||
]
|
||||
```
|
||||
|
||||
Please note that the Pleroma developers consider custom MRF policy modules to fall under the purview of the AGPL. As such, you are obligated to release the sources to your custom MRF policy modules upon request.
|
||||
Please note that the Pleroma developers consider custom MRF policy modules to fall under the purview of the AGPL. As such, you are obligated to release the sources to your custom MRF policy modules upon request.
|
||||
|
|
|
@ -259,19 +259,14 @@ su pleroma -s $SHELL -lc "./bin/pleroma_ctl user new joeuser joeuser@sld.tld --a
|
|||
```
|
||||
This will create an account withe the username of 'joeuser' with the email address of joeuser@sld.tld, and set that user's account as an admin. This will result in a link that you can paste into the browser, which logs you in and enables you to set the password.
|
||||
|
||||
### Updating
|
||||
Generally, doing the following is enough:
|
||||
```sh
|
||||
# Download the new release
|
||||
su pleroma -s $SHELL -lc "./bin/pleroma_ctl update"
|
||||
|
||||
# Migrate the database, you are advised to stop the instance before doing that
|
||||
su pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate"
|
||||
```
|
||||
But you should **always check the release notes/changelog** in case there are config deprecations, special update steps, etc.
|
||||
|
||||
## Further reading
|
||||
|
||||
* [Backup your instance](../administration/backup.md)
|
||||
* [Hardening your instance](../configuration/hardening.md)
|
||||
* [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
|
||||
* [Updating your instance](../administration/updating.md)
|
||||
|
||||
## Questions
|
||||
|
||||
Questions about the installation or didn’t it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ On the top right you will also see a wrench icon. This opens your personal setti
|
|||
This is where the interesting stuff happens!
|
||||
Depending on the timeline you will see different statuses, but each status has a standard structure:
|
||||
|
||||
- Profile pic, name and link to profile. An optional left-arrow if it's a reply to another status (hovering will reveal the replied-to status). Clicking on the profile pic will uncollapse the user's profile.
|
||||
- Profile pic, name and link to profile. An optional left-arrow if it's a reply to another status (hovering will reveal the reply-to status). Clicking on the profile pic will uncollapse the user's profile.
|
||||
- A `+` button on the right allows you to Expand/Collapse an entire discussion thread. It also updates in realtime!
|
||||
- An arrow icon allows you to open the status on the instance where it's originating from.
|
||||
- The text of the status, including mentions and attachements. If you click on a mention, it will automatically open the profile page of that person.
|
||||
|
|
|
@ -6,6 +6,8 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
use Mix.Task
|
||||
import Mix.Pleroma
|
||||
|
||||
alias Pleroma.Config
|
||||
|
||||
@shortdoc "Manages Pleroma instance"
|
||||
@moduledoc File.read!("docs/administration/CLI_tasks/instance.md")
|
||||
|
||||
|
@ -153,6 +155,8 @@ def run(["gen" | rest]) do
|
|||
Pleroma.Config.get([:instance, :static_dir])
|
||||
)
|
||||
|
||||
Config.put([:instance, :static_dir], static_dir)
|
||||
|
||||
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)
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mix.Tasks.Pleroma.RefreshCounterCache do
|
||||
@shortdoc "Refreshes counter cache"
|
||||
|
||||
use Mix.Task
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.CounterCache
|
||||
alias Pleroma.Repo
|
||||
|
||||
require Logger
|
||||
import Ecto.Query
|
||||
|
||||
def run([]) do
|
||||
Mix.Pleroma.start_pleroma()
|
||||
|
||||
["public", "unlisted", "private", "direct"]
|
||||
|> Enum.each(fn visibility ->
|
||||
count = status_visibility_count_query(visibility)
|
||||
name = "status_visibility_#{visibility}"
|
||||
CounterCache.set(name, count)
|
||||
Mix.Pleroma.shell_info("Set #{name} to #{count}")
|
||||
end)
|
||||
|
||||
Mix.Pleroma.shell_info("Done")
|
||||
end
|
||||
|
||||
defp status_visibility_count_query(visibility) do
|
||||
Activity
|
||||
|> where(
|
||||
[a],
|
||||
fragment(
|
||||
"activity_visibility(?, ?, ?) = ?",
|
||||
a.actor,
|
||||
a.recipients,
|
||||
a.data,
|
||||
^visibility
|
||||
)
|
||||
)
|
||||
|> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data))
|
||||
|> Repo.aggregate(:count, :id, timeout: :timer.minutes(30))
|
||||
end
|
||||
end
|
|
@ -7,7 +7,7 @@ defmodule Pleroma.Activity.Queries do
|
|||
Contains queries for Activity.
|
||||
"""
|
||||
|
||||
import Ecto.Query, only: [from: 2]
|
||||
import Ecto.Query, only: [from: 2, where: 3]
|
||||
|
||||
@type query :: Ecto.Queryable.t() | Activity.t()
|
||||
|
||||
|
@ -63,6 +63,22 @@ def by_object_id(query, object_id) when is_binary(object_id) do
|
|||
)
|
||||
end
|
||||
|
||||
@spec by_object_in_reply_to_id(query, String.t(), keyword()) :: query
|
||||
def by_object_in_reply_to_id(query, in_reply_to_id, opts \\ []) do
|
||||
query =
|
||||
if opts[:skip_preloading] do
|
||||
Activity.with_joined_object(query)
|
||||
else
|
||||
Activity.with_preloaded_object(query)
|
||||
end
|
||||
|
||||
where(
|
||||
query,
|
||||
[activity, object: o],
|
||||
fragment("(?)->>'inReplyTo' = ?", o.data, ^to_string(in_reply_to_id))
|
||||
)
|
||||
end
|
||||
|
||||
@spec by_type(query, String.t()) :: query
|
||||
def by_type(query \\ Activity, activity_type) do
|
||||
from(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Captcha do
|
||||
|
@ -50,7 +50,7 @@ def handle_call(:new, _from, state) do
|
|||
token = new_captcha[:token]
|
||||
secret = KeyGenerator.generate(secret_key_base, token <> "_encrypt")
|
||||
sign_secret = KeyGenerator.generate(secret_key_base, token <> "_sign")
|
||||
# Basicallty copy what Phoenix.Token does here, add the time to
|
||||
# Basically copy what Phoenix.Token does here, add the time to
|
||||
# the actual data and make it a binary to then encrypt it
|
||||
encrypted_captcha_answer =
|
||||
%{
|
||||
|
@ -62,7 +62,7 @@ def handle_call(:new, _from, state) do
|
|||
|
||||
{
|
||||
:reply,
|
||||
# Repalce the answer with the encrypted answer
|
||||
# Replace the answer with the encrypted answer
|
||||
%{new_captcha | answer_data: encrypted_captcha_answer},
|
||||
state
|
||||
}
|
||||
|
@ -82,7 +82,8 @@ def handle_call({:validate, token, captcha, answer_data}, _from, state) do
|
|||
valid_if_after = DateTime.subtract!(DateTime.now_utc(), seconds_valid)
|
||||
|
||||
result =
|
||||
with {:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret),
|
||||
with false <- is_nil(answer_data),
|
||||
{:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret),
|
||||
%{at: at, answer_data: answer_md5} <- :erlang.binary_to_term(data) do
|
||||
try do
|
||||
if DateTime.before?(at, valid_if_after),
|
||||
|
|
|
@ -146,9 +146,7 @@ defp group_and_subkey_need_reboot?(group, key, value) do
|
|||
defp update_env(group, key, nil), do: Application.delete_env(group, key)
|
||||
defp update_env(group, key, value), do: Application.put_env(group, key, value)
|
||||
|
||||
defp restart(_, :pleroma, :test), do: Logger.warn("pleroma restarted")
|
||||
|
||||
defp restart(_, :pleroma, _), do: send(Restarter.Pleroma, :after_boot)
|
||||
defp restart(_, :pleroma, env), do: Restarter.Pleroma.restart_after_boot(env)
|
||||
|
||||
defp restart(started_applications, app, _) do
|
||||
with {^app, _, _} <- List.keyfind(started_applications, app, 0),
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.CounterCache do
|
||||
alias Pleroma.CounterCache
|
||||
alias Pleroma.Repo
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
|
||||
schema "counter_cache" do
|
||||
field(:name, :string)
|
||||
field(:count, :integer)
|
||||
end
|
||||
|
||||
def changeset(struct, params) do
|
||||
struct
|
||||
|> cast(params, [:name, :count])
|
||||
|> validate_required([:name])
|
||||
|> unique_constraint(:name)
|
||||
end
|
||||
|
||||
def get_as_map(names) when is_list(names) do
|
||||
CounterCache
|
||||
|> where([cc], cc.name in ^names)
|
||||
|> Repo.all()
|
||||
|> Enum.group_by(& &1.name, & &1.count)
|
||||
|> Map.new(fn {k, v} -> {k, hd(v)} end)
|
||||
end
|
||||
|
||||
def set(name, count) do
|
||||
%CounterCache{}
|
||||
|> changeset(%{"name" => name, "count" => count})
|
||||
|> Repo.insert(
|
||||
on_conflict: [set: [count: count]],
|
||||
returning: true,
|
||||
conflict_target: :name
|
||||
)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,32 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Emails.NewUsersDigestEmail do
|
||||
use Phoenix.Swoosh, view: Pleroma.Web.EmailView, layout: {Pleroma.Web.LayoutView, :email_styled}
|
||||
|
||||
defp instance_notify_email do
|
||||
Pleroma.Config.get([:instance, :notify_email]) || Pleroma.Config.get([:instance, :email])
|
||||
end
|
||||
|
||||
def new_users(to, users_and_statuses) do
|
||||
instance_name = Pleroma.Config.get([:instance, :name])
|
||||
styling = Pleroma.Config.get([Pleroma.Emails.UserEmail, :styling])
|
||||
|
||||
logo_url =
|
||||
Pleroma.Web.Endpoint.url() <>
|
||||
Pleroma.Config.get([:frontend_configurations, :pleroma_fe, :logo])
|
||||
|
||||
new()
|
||||
|> to({to.name, to.email})
|
||||
|> from({instance_name, instance_notify_email()})
|
||||
|> subject("#{instance_name} New Users")
|
||||
|> render_body("new_users_digest.html", %{
|
||||
title: "New Users",
|
||||
users_and_statuses: users_and_statuses,
|
||||
instance: instance_name,
|
||||
styling: styling,
|
||||
logo_url: logo_url
|
||||
})
|
||||
end
|
||||
end
|
|
@ -108,6 +108,7 @@ def extract_first_external_url(object, content) do
|
|||
Cachex.fetch!(:scrubber_cache, key, fn _key ->
|
||||
result =
|
||||
content
|
||||
|> Floki.parse_fragment!()
|
||||
|> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"]")
|
||||
|> Floki.attribute("a", "href")
|
||||
|> Enum.at(0)
|
||||
|
|
|
@ -301,4 +301,26 @@ def update_data(%Object{data: data} = object, attrs \\ %{}) do
|
|||
def local?(%Object{data: %{"id" => id}}) do
|
||||
String.starts_with?(id, Pleroma.Web.base_url() <> "/")
|
||||
end
|
||||
|
||||
def replies(object, opts \\ []) do
|
||||
object = Object.normalize(object)
|
||||
|
||||
query =
|
||||
Object
|
||||
|> where(
|
||||
[o],
|
||||
fragment("(?)->>'inReplyTo' = ?", o.data, ^object.data["id"])
|
||||
)
|
||||
|> order_by([o], asc: o.id)
|
||||
|
||||
if opts[:self_only] do
|
||||
actor = object.data["actor"]
|
||||
where(query, [o], fragment("(?)->>'actor' = ?", o.data, ^actor))
|
||||
else
|
||||
query
|
||||
end
|
||||
end
|
||||
|
||||
def self_replies(object, opts \\ []),
|
||||
do: replies(object, Keyword.put(opts, :self_only, true))
|
||||
end
|
||||
|
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Object.Fetcher do
|
|||
alias Pleroma.Signature
|
||||
alias Pleroma.Web.ActivityPub.InternalFetchActor
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.Federator
|
||||
|
||||
require Logger
|
||||
require Pleroma.Constants
|
||||
|
@ -59,20 +60,23 @@ def refetch_object(%Object{data: %{"id" => id}} = object) do
|
|||
end
|
||||
end
|
||||
|
||||
# TODO:
|
||||
# This will create a Create activity, which we need internally at the moment.
|
||||
# Note: will create a Create activity, which we need internally at the moment.
|
||||
def fetch_object_from_id(id, options \\ []) do
|
||||
with {:fetch_object, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)},
|
||||
{:fetch, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)},
|
||||
{:normalize, nil} <- {:normalize, Object.normalize(data, false)},
|
||||
with {_, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)},
|
||||
{_, true} <- {:allowed_depth, Federator.allowed_thread_distance?(options[:depth])},
|
||||
{_, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)},
|
||||
{_, nil} <- {:normalize, Object.normalize(data, false)},
|
||||
params <- prepare_activity_params(data),
|
||||
{:containment, :ok} <- {:containment, Containment.contain_origin(id, params)},
|
||||
{:transmogrifier, {:ok, activity}} <-
|
||||
{_, :ok} <- {:containment, Containment.contain_origin(id, params)},
|
||||
{_, {:ok, activity}} <-
|
||||
{:transmogrifier, Transmogrifier.handle_incoming(params, options)},
|
||||
{:object, _data, %Object{} = object} <-
|
||||
{_, _data, %Object{} = object} <-
|
||||
{:object, data, Object.normalize(activity, false)} do
|
||||
{:ok, object}
|
||||
else
|
||||
{:allowed_depth, false} ->
|
||||
{:error, "Max thread distance exceeded."}
|
||||
|
||||
{:containment, _} ->
|
||||
{:error, "Object containment failed."}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
|
||||
import Plug.Conn
|
||||
import Phoenix.Controller, only: [get_format: 1, text: 2]
|
||||
require Logger
|
||||
|
||||
def init(options) do
|
||||
|
@ -15,25 +16,27 @@ def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
|
|||
end
|
||||
|
||||
def call(conn, _opts) do
|
||||
headers = get_req_header(conn, "signature")
|
||||
signature = Enum.at(headers, 0)
|
||||
if get_format(conn) == "activity+json" do
|
||||
conn
|
||||
|> maybe_assign_valid_signature()
|
||||
|> maybe_require_signature()
|
||||
else
|
||||
conn
|
||||
end
|
||||
end
|
||||
|
||||
if signature do
|
||||
defp maybe_assign_valid_signature(conn) do
|
||||
if has_signature_header?(conn) do
|
||||
# set (request-target) header to the appropriate value
|
||||
# we also replace the digest header with the one we computed
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header(
|
||||
"(request-target)",
|
||||
String.downcase("#{conn.method}") <> " #{conn.request_path}"
|
||||
)
|
||||
request_target = String.downcase("#{conn.method}") <> " #{conn.request_path}"
|
||||
|
||||
conn =
|
||||
if conn.assigns[:digest] do
|
||||
conn
|
||||
|> put_req_header("digest", conn.assigns[:digest])
|
||||
else
|
||||
conn
|
||||
conn
|
||||
|> put_req_header("(request-target)", request_target)
|
||||
|> case do
|
||||
%{assigns: %{digest: digest}} = conn -> put_req_header(conn, "digest", digest)
|
||||
conn -> conn
|
||||
end
|
||||
|
||||
assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn))
|
||||
|
@ -42,4 +45,21 @@ def call(conn, _opts) do
|
|||
conn
|
||||
end
|
||||
end
|
||||
|
||||
defp has_signature_header?(conn) do
|
||||
conn |> get_req_header("signature") |> Enum.at(0, false)
|
||||
end
|
||||
|
||||
defp maybe_require_signature(%{assigns: %{valid_signature: true}} = conn), do: conn
|
||||
|
||||
defp maybe_require_signature(conn) do
|
||||
if Pleroma.Config.get([:activitypub, :authorized_fetch_mode], false) do
|
||||
conn
|
||||
|> put_status(:unauthorized)
|
||||
|> text("Request not signed")
|
||||
|> halt()
|
||||
else
|
||||
conn
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
defmodule Pleroma.Stats do
|
||||
import Ecto.Query
|
||||
alias Pleroma.CounterCache
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
|
@ -96,4 +97,21 @@ defp get_stat_data do
|
|||
}
|
||||
}
|
||||
end
|
||||
|
||||
def get_status_visibility_count do
|
||||
counter_cache =
|
||||
CounterCache.get_as_map([
|
||||
"status_visibility_public",
|
||||
"status_visibility_private",
|
||||
"status_visibility_unlisted",
|
||||
"status_visibility_direct"
|
||||
])
|
||||
|
||||
%{
|
||||
public: counter_cache["status_visibility_public"] || 0,
|
||||
unlisted: counter_cache["status_visibility_unlisted"] || 0,
|
||||
private: counter_cache["status_visibility_private"] || 0,
|
||||
direct: counter_cache["status_visibility_direct"] || 0
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,6 +17,7 @@ defp old_user?(%User{} = u) do
|
|||
# does the post contain links?
|
||||
defp contains_links?(%{"content" => content} = _object) do
|
||||
content
|
||||
|> Floki.parse_fragment!()
|
||||
|> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"],a.zrl")
|
||||
|> Floki.attribute("a", "href")
|
||||
|> length() > 0
|
||||
|
|
|
@ -5,12 +5,11 @@
|
|||
defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
require Pleroma.Constants
|
||||
|
||||
@moduledoc "Filter activities depending on their age"
|
||||
@behaviour MRF
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
defp check_date(%{"published" => published} = message) do
|
||||
with %DateTime{} = now <- DateTime.utc_now(),
|
||||
|
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.MRF
|
||||
@moduledoc "Filter activities depending on their origin instance"
|
||||
@behaviour MRF
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
require Pleroma.Constants
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
|
|||
|
||||
require Logger
|
||||
|
||||
@behaviour MRF
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
defp lookup_subchain(actor) do
|
||||
with matches <- Config.get([:mrf_subchain, :match_actor]),
|
||||
|
|
|
@ -156,8 +156,9 @@ def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
|
|||
when not is_nil(in_reply_to) do
|
||||
in_reply_to_id = prepare_in_reply_to(in_reply_to)
|
||||
object = Map.put(object, "inReplyToAtomUri", in_reply_to_id)
|
||||
depth = (options[:depth] || 0) + 1
|
||||
|
||||
if Federator.allowed_incoming_reply_depth?(options[:depth]) do
|
||||
if Federator.allowed_thread_distance?(depth) do
|
||||
with {:ok, replied_object} <- get_obj_helper(in_reply_to_id, options),
|
||||
%Activity{} = _ <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
|
||||
object
|
||||
|
@ -312,7 +313,7 @@ def fix_type(object, options \\ [])
|
|||
|
||||
def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options)
|
||||
when is_binary(reply_id) do
|
||||
with true <- Federator.allowed_incoming_reply_depth?(options[:depth]),
|
||||
with true <- Federator.allowed_thread_distance?(options[:depth]),
|
||||
{:ok, %{data: %{"type" => "Question"} = _} = _} <- get_obj_helper(reply_id, options) do
|
||||
Map.put(object, "type", "Answer")
|
||||
else
|
||||
|
@ -406,8 +407,7 @@ def handle_incoming(
|
|||
|
||||
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
|
||||
{:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do
|
||||
options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
|
||||
object = fix_object(data["object"], options)
|
||||
object = fix_object(object, options)
|
||||
|
||||
params = %{
|
||||
to: data["to"],
|
||||
|
@ -424,7 +424,20 @@ def handle_incoming(
|
|||
])
|
||||
}
|
||||
|
||||
ActivityPub.create(params)
|
||||
with {:ok, created_activity} <- ActivityPub.create(params) do
|
||||
reply_depth = (options[:depth] || 0) + 1
|
||||
|
||||
if Federator.allowed_thread_distance?(reply_depth) do
|
||||
for reply_id <- replies(object) do
|
||||
Pleroma.Workers.RemoteFetcherWorker.enqueue("fetch_remote", %{
|
||||
"id" => reply_id,
|
||||
"depth" => reply_depth
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
{:ok, created_activity}
|
||||
end
|
||||
else
|
||||
%Activity{} = activity -> {:ok, activity}
|
||||
_e -> :error
|
||||
|
@ -442,7 +455,8 @@ def handle_incoming(
|
|||
|> fix_addressing
|
||||
|
||||
with {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do
|
||||
options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
|
||||
reply_depth = (options[:depth] || 0) + 1
|
||||
options = Keyword.put(options, :depth, reply_depth)
|
||||
object = fix_object(object, options)
|
||||
|
||||
params = %{
|
||||
|
@ -903,6 +917,50 @@ def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_r
|
|||
|
||||
def set_reply_to_uri(obj), do: obj
|
||||
|
||||
@doc """
|
||||
Serialized Mastodon-compatible `replies` collection containing _self-replies_.
|
||||
Based on Mastodon's ActivityPub::NoteSerializer#replies.
|
||||
"""
|
||||
def set_replies(obj_data) do
|
||||
replies_uris =
|
||||
with limit when limit > 0 <-
|
||||
Pleroma.Config.get([:activitypub, :note_replies_output_limit], 0),
|
||||
%Object{} = object <- Object.get_cached_by_ap_id(obj_data["id"]) do
|
||||
object
|
||||
|> Object.self_replies()
|
||||
|> select([o], fragment("?->>'id'", o.data))
|
||||
|> limit(^limit)
|
||||
|> Repo.all()
|
||||
else
|
||||
_ -> []
|
||||
end
|
||||
|
||||
set_replies(obj_data, replies_uris)
|
||||
end
|
||||
|
||||
defp set_replies(obj, []) do
|
||||
obj
|
||||
end
|
||||
|
||||
defp set_replies(obj, replies_uris) do
|
||||
replies_collection = %{
|
||||
"type" => "Collection",
|
||||
"items" => replies_uris
|
||||
}
|
||||
|
||||
Map.merge(obj, %{"replies" => replies_collection})
|
||||
end
|
||||
|
||||
def replies(%{"replies" => %{"first" => %{"items" => items}}}) when not is_nil(items) do
|
||||
items
|
||||
end
|
||||
|
||||
def replies(%{"replies" => %{"items" => items}}) when not is_nil(items) do
|
||||
items
|
||||
end
|
||||
|
||||
def replies(_), do: []
|
||||
|
||||
# Prepares the object of an outgoing create activity.
|
||||
def prepare_object(object) do
|
||||
object
|
||||
|
@ -914,6 +972,7 @@ def prepare_object(object) do
|
|||
|> prepare_attachments
|
||||
|> set_conversation
|
||||
|> set_reply_to_uri
|
||||
|> set_replies
|
||||
|> strip_internal_fields
|
||||
|> strip_internal_tags
|
||||
|> set_type
|
||||
|
|
|
@ -8,10 +8,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.ConfigDB
|
||||
alias Pleroma.ModerationLog
|
||||
alias Pleroma.Plugs.OAuthScopesPlug
|
||||
alias Pleroma.ReportNote
|
||||
alias Pleroma.Stats
|
||||
alias Pleroma.User
|
||||
alias Pleroma.UserInviteToken
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
|
@ -97,7 +99,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["read"], admin: true}
|
||||
when action in [:config_show, :list_log]
|
||||
when action in [:config_show, :list_log, :stats]
|
||||
)
|
||||
|
||||
plug(
|
||||
|
@ -570,8 +572,8 @@ def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target})
|
|||
@doc "Sends registration invite via email"
|
||||
def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
|
||||
with true <-
|
||||
Pleroma.Config.get([:instance, :invites_enabled]) &&
|
||||
!Pleroma.Config.get([:instance, :registrations_open]),
|
||||
Config.get([:instance, :invites_enabled]) &&
|
||||
!Config.get([:instance, :registrations_open]),
|
||||
{:ok, invite_token} <- UserInviteToken.create_invite(),
|
||||
email <-
|
||||
Pleroma.Emails.UserEmail.user_invitation_email(
|
||||
|
@ -808,7 +810,7 @@ def config_show(conn, _params) do
|
|||
configs = ConfigDB.get_all_as_keyword()
|
||||
|
||||
merged =
|
||||
Pleroma.Config.Holder.config()
|
||||
Config.Holder.config()
|
||||
|> ConfigDB.merge(configs)
|
||||
|> Enum.map(fn {group, values} ->
|
||||
Enum.map(values, fn {key, value} ->
|
||||
|
@ -838,7 +840,16 @@ def config_show(conn, _params) do
|
|||
end)
|
||||
|> List.flatten()
|
||||
|
||||
json(conn, %{configs: merged})
|
||||
response = %{configs: merged}
|
||||
|
||||
response =
|
||||
if Restarter.Pleroma.need_reboot?() do
|
||||
Map.put(response, :need_reboot, true)
|
||||
else
|
||||
response
|
||||
end
|
||||
|
||||
json(conn, response)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -863,20 +874,26 @@ def config_update(conn, %{"configs" => configs}) do
|
|||
Ecto.get_meta(config, :state) == :deleted
|
||||
end)
|
||||
|
||||
Pleroma.Config.TransferTask.load_and_update_env(deleted, false)
|
||||
Config.TransferTask.load_and_update_env(deleted, false)
|
||||
|
||||
need_reboot? =
|
||||
Enum.any?(updated, fn config ->
|
||||
group = ConfigDB.from_string(config.group)
|
||||
key = ConfigDB.from_string(config.key)
|
||||
value = ConfigDB.from_binary(config.value)
|
||||
Pleroma.Config.TransferTask.pleroma_need_restart?(group, key, value)
|
||||
end)
|
||||
Restarter.Pleroma.need_reboot?() ||
|
||||
Enum.any?(updated, fn config ->
|
||||
group = ConfigDB.from_string(config.group)
|
||||
key = ConfigDB.from_string(config.key)
|
||||
value = ConfigDB.from_binary(config.value)
|
||||
Config.TransferTask.pleroma_need_restart?(group, key, value)
|
||||
end)
|
||||
|
||||
response = %{configs: updated}
|
||||
|
||||
response =
|
||||
if need_reboot?, do: Map.put(response, :need_reboot, need_reboot?), else: response
|
||||
if need_reboot? do
|
||||
Restarter.Pleroma.need_reboot()
|
||||
Map.put(response, :need_reboot, need_reboot?)
|
||||
else
|
||||
response
|
||||
end
|
||||
|
||||
conn
|
||||
|> put_view(ConfigView)
|
||||
|
@ -886,18 +903,14 @@ def config_update(conn, %{"configs" => configs}) do
|
|||
|
||||
def restart(conn, _params) do
|
||||
with :ok <- configurable_from_database(conn) do
|
||||
if Pleroma.Config.get(:env) == :test do
|
||||
Logger.warn("pleroma restarted")
|
||||
else
|
||||
send(Restarter.Pleroma, {:restart, 50})
|
||||
end
|
||||
Restarter.Pleroma.restart(Config.get(:env), 50)
|
||||
|
||||
json(conn, %{})
|
||||
end
|
||||
end
|
||||
|
||||
defp configurable_from_database(conn) do
|
||||
if Pleroma.Config.get(:configurable_from_database) do
|
||||
if Config.get(:configurable_from_database) do
|
||||
:ok
|
||||
else
|
||||
errors(
|
||||
|
@ -941,6 +954,13 @@ def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" =
|
|||
conn |> json("")
|
||||
end
|
||||
|
||||
def stats(conn, _) do
|
||||
count = Stats.get_status_visibility_count()
|
||||
|
||||
conn
|
||||
|> json(%{"status_visibility" => count})
|
||||
end
|
||||
|
||||
def errors(conn, {:error, :not_found}) do
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
|
|
@ -15,13 +15,19 @@ defmodule Pleroma.Web.Federator do
|
|||
|
||||
require Logger
|
||||
|
||||
@doc "Addresses [memory leaks on recursive replies fetching](https://git.pleroma.social/pleroma/pleroma/issues/161)"
|
||||
@doc """
|
||||
Returns `true` if the distance to target object does not exceed max configured value.
|
||||
Serves to prevent fetching of very long threads, especially useful on smaller instances.
|
||||
Addresses [memory leaks on recursive replies fetching](https://git.pleroma.social/pleroma/pleroma/issues/161).
|
||||
Applies to fetching of both ancestor (reply-to) and child (reply) objects.
|
||||
"""
|
||||
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
|
||||
def allowed_incoming_reply_depth?(depth) do
|
||||
max_replies_depth = Pleroma.Config.get([:instance, :federation_incoming_replies_max_depth])
|
||||
def allowed_thread_distance?(distance) do
|
||||
max_distance = Pleroma.Config.get([:instance, :federation_incoming_replies_max_depth])
|
||||
|
||||
if max_replies_depth do
|
||||
(depth || 1) <= max_replies_depth
|
||||
if max_distance && max_distance >= 0 do
|
||||
# Default depth is 0 (an object has zero distance from itself in its thread)
|
||||
(distance || 0) <= max_distance
|
||||
else
|
||||
true
|
||||
end
|
||||
|
|
|
@ -175,9 +175,11 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
|||
|
||||
expires_at =
|
||||
with true <- client_posted_this_activity,
|
||||
expiration when not is_nil(expiration) <-
|
||||
%ActivityExpiration{scheduled_at: scheduled_at} <-
|
||||
ActivityExpiration.get_by_activity_id(activity.id) do
|
||||
expiration.scheduled_at
|
||||
scheduled_at
|
||||
else
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
thread_muted? =
|
||||
|
|
|
@ -8,8 +8,10 @@ defmodule Pleroma.Web.Metadata.Providers.RelMe do
|
|||
|
||||
@impl Provider
|
||||
def build_tags(%{user: user}) do
|
||||
(Floki.attribute(user.bio, "link[rel~=me]", "href") ++
|
||||
Floki.attribute(user.bio, "a[rel~=me]", "href"))
|
||||
bio_tree = Floki.parse_fragment!(user.bio)
|
||||
|
||||
(Floki.attribute(bio_tree, "link[rel~=me]", "href") ++
|
||||
Floki.attribute(bio_tree, "a[rel~=me]", "href"))
|
||||
|> Enum.map(fn link ->
|
||||
{:link, [rel: "me", href: link], []}
|
||||
end)
|
||||
|
|
|
@ -41,24 +41,29 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
|
|||
|
||||
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
|
||||
|
||||
def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do
|
||||
def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id} = params) do
|
||||
with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
|
||||
%Object{data: %{"reactions" => emoji_reactions}} when is_list(emoji_reactions) <-
|
||||
Object.normalize(activity) do
|
||||
reactions =
|
||||
emoji_reactions
|
||||
|> Enum.map(fn [emoji, user_ap_ids] ->
|
||||
users =
|
||||
Enum.map(user_ap_ids, &User.get_cached_by_ap_id/1)
|
||||
|> Enum.filter(& &1)
|
||||
if params["emoji"] && params["emoji"] != emoji do
|
||||
nil
|
||||
else
|
||||
users =
|
||||
Enum.map(user_ap_ids, &User.get_cached_by_ap_id/1)
|
||||
|> Enum.filter(& &1)
|
||||
|
||||
%{
|
||||
name: emoji,
|
||||
count: length(users),
|
||||
accounts: AccountView.render("index.json", %{users: users, for: user, as: :user}),
|
||||
me: !!(user && user.ap_id in user_ap_ids)
|
||||
}
|
||||
%{
|
||||
name: emoji,
|
||||
count: length(users),
|
||||
accounts: AccountView.render("index.json", %{users: users, for: user, as: :user}),
|
||||
me: !!(user && user.ap_id in user_ap_ids)
|
||||
}
|
||||
end
|
||||
end)
|
||||
|> Enum.filter(& &1)
|
||||
|
||||
conn
|
||||
|> json(reactions)
|
||||
|
|
|
@ -27,9 +27,10 @@ def parse(_), do: {:error, "No URL provided"}
|
|||
defp parse_url(url) do
|
||||
with {:ok, %Tesla.Env{body: html, status: status}} when status in 200..299 <-
|
||||
Pleroma.HTTP.get(url, [], adapter: @hackney_options),
|
||||
{:ok, html_tree} <- Floki.parse_document(html),
|
||||
data <-
|
||||
Floki.attribute(html, "link[rel~=me]", "href") ++
|
||||
Floki.attribute(html, "a[rel~=me]", "href") do
|
||||
Floki.attribute(html_tree, "link[rel~=me]", "href") ++
|
||||
Floki.attribute(html_tree, "a[rel~=me]", "href") do
|
||||
{:ok, data}
|
||||
end
|
||||
rescue
|
||||
|
|
|
@ -81,18 +81,18 @@ defp parse_url(url) do
|
|||
{:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options)
|
||||
|
||||
html
|
||||
|> parse_html
|
||||
|> parse_html()
|
||||
|> maybe_parse()
|
||||
|> Map.put(:url, url)
|
||||
|> clean_parsed_data()
|
||||
|> check_parsed_data()
|
||||
rescue
|
||||
e ->
|
||||
{:error, "Parsing error: #{inspect(e)}"}
|
||||
{:error, "Parsing error: #{inspect(e)} #{inspect(__STACKTRACE__)}"}
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_html(html), do: Floki.parse(html)
|
||||
defp parse_html(html), do: Floki.parse_document!(html)
|
||||
|
||||
defp maybe_parse(html) do
|
||||
Enum.reduce_while(parsers(), %{}, fn parser, acc ->
|
||||
|
|
|
@ -201,6 +201,7 @@ defmodule Pleroma.Web.Router do
|
|||
get("/moderation_log", AdminAPIController, :list_log)
|
||||
|
||||
post("/reload_emoji", AdminAPIController, :reload_emoji)
|
||||
get("/stats", AdminAPIController, :stats)
|
||||
end
|
||||
|
||||
scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
|
||||
|
@ -271,6 +272,7 @@ defmodule Pleroma.Web.Router do
|
|||
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
|
||||
pipe_through(:api)
|
||||
|
||||
get("/statuses/:id/reactions/:emoji", PleromaAPIController, :emoji_reactions_by)
|
||||
get("/statuses/:id/reactions", PleromaAPIController, :emoji_reactions_by)
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
<%= for {user, total_statuses, latest_status} <- @users_and_statuses do %>
|
||||
<%# user card START %>
|
||||
<div style="background-color:transparent;">
|
||||
<div class="block-grid mixed-two-up no-stack"
|
||||
style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td align="center" width="147" style="background-color:<%= @styling.content_background_color%>;width:76px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 20px; padding-top:5px; padding-bottom:5px;"><![endif]-->
|
||||
<div class="col num3"
|
||||
style="display: table-cell; vertical-align: top; max-width: 320px; min-width: 76px; width: 76px;">
|
||||
<div style="width:100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
<div
|
||||
style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 20px;">
|
||||
<!--<![endif]-->
|
||||
<div align="left" class="img-container left "
|
||||
style="padding-right: 0px;padding-left: 0px;">
|
||||
<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr style="line-height:0px"><td style="padding-right: 0px;padding-left: 0px;" align="left"><![endif]--><img
|
||||
alt="<%= user.name %>" border="0" class="left " src="<%= avatar_url(user) %>"
|
||||
style="text-decoration: none; -ms-interpolation-mode: bicubic; border: 0; height: auto; width: 100%; max-width: 76px; display: block;"
|
||||
title="<%= user.name %>" width="76" />
|
||||
<!--[if mso]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div>
|
||||
<!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--[if (mso)|(IE)]></td></tr></table><![endif]-->
|
||||
<!--[if (mso)|(IE)]></td><td align="center" width="442" style="background-color:<%= @styling.content_background_color%>;width:442px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]-->
|
||||
<div class="col num9"
|
||||
style="display: table-cell; vertical-align: top; min-width: 320px; max-width: 441px; width: 442px;">
|
||||
<div style="width:100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
<div
|
||||
style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;">
|
||||
<!--<![endif]-->
|
||||
<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]-->
|
||||
<div
|
||||
style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;">
|
||||
<div
|
||||
style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_color %>;">
|
||||
<p style="font-size: 14px; line-height: 19px; margin: 0;"><span
|
||||
style="font-size: 16px; color: <%= @styling.text_color %>;"><%= user.name %></span></p>
|
||||
<p style="font-size: 14px; line-height: 19px; margin: 0;"><span
|
||||
style="font-size: 16px;"><%= link "@" <> user.nickname, style: "color: #{@styling.link_color};text-decoration: none;", to: admin_user_url(user) %></span></p>
|
||||
<p style="font-size: 14px; line-height: 19px; margin: 0;"><span
|
||||
style="font-size: 16px;">Total: <%= total_statuses %></span></p>
|
||||
</div>
|
||||
</div>
|
||||
<!--[if mso]></td></tr></table><![endif]-->
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div>
|
||||
<!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td></tr></table><![endif]-->
|
||||
<!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%# user card END %>
|
||||
|
||||
<%= if latest_status do %>
|
||||
<div style="background-color:transparent;">
|
||||
<div class="block-grid"
|
||||
style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 15px; padding-left: 15px; padding-top:5px; padding-bottom:5px;"><![endif]-->
|
||||
<div class="col num12"
|
||||
style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;">
|
||||
<div style="width:100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
<div
|
||||
style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 15px; padding-left: 15px;">
|
||||
<!--<![endif]-->
|
||||
<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]-->
|
||||
<div
|
||||
style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;">
|
||||
<div
|
||||
style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_color %>;">
|
||||
<span style="font-size: 16px; line-height: 19px;"><%= raw latest_status.object.data["content"] %></span></div>
|
||||
</div>
|
||||
<!--[if mso]></td></tr></table><![endif]-->
|
||||
<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 15px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]-->
|
||||
<div
|
||||
style="color:<%= @styling.text_muted_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:15px;">
|
||||
<div
|
||||
style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_muted_color %>;">
|
||||
<p style="font-size: 14px; line-height: 16px; margin: 0;"><%= format_date latest_status.object.data["published"] %></p>
|
||||
</div>
|
||||
</div>
|
||||
<!--[if mso]></td></tr></table><![endif]-->
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div>
|
||||
<!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td></tr></table><![endif]-->
|
||||
<!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<%# divider start %>
|
||||
<div style="background-color:transparent;">
|
||||
<div class="block-grid"
|
||||
style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]-->
|
||||
<div class="col num12"
|
||||
style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;">
|
||||
<div style="width:100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
<div
|
||||
style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;">
|
||||
<!--<![endif]-->
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="divider" role="presentation"
|
||||
style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;"
|
||||
valign="top" width="100%">
|
||||
<tbody>
|
||||
<tr style="vertical-align: top;" valign="top">
|
||||
<td class="divider_inner"
|
||||
style="word-break: break-word; vertical-align: top; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px;"
|
||||
valign="top">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" class="divider_content"
|
||||
height="0" role="presentation"
|
||||
style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; border-top: 1px solid <%= @styling.text_color %>; height: 0px;"
|
||||
valign="top" width="100%">
|
||||
<tbody>
|
||||
<tr style="vertical-align: top;" valign="top">
|
||||
<td height="0"
|
||||
style="word-break: break-word; vertical-align: top; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;"
|
||||
valign="top"><span></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div>
|
||||
<!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td></tr></table><![endif]-->
|
||||
<!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%# divider end %>
|
||||
<%# user card END %>
|
||||
<% end %>
|
|
@ -0,0 +1,193 @@
|
|||
<!DOCTYPE html
|
||||
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office"
|
||||
xmlns:v="urn:schemas-microsoft-com:vml">
|
||||
|
||||
<head>
|
||||
<!--[if gte mso 9]><xml><o:OfficeDocumentSettings><o:AllowPNG/><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml><![endif]-->
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
|
||||
<meta content="width=device-width" name="viewport" />
|
||||
<!--[if !mso]><!-->
|
||||
<meta content="IE=edge" http-equiv="X-UA-Compatible" />
|
||||
<!--<![endif]-->
|
||||
<title><%= @email.subject %></title>
|
||||
<!--[if !mso]><!-->
|
||||
<!--<![endif]-->
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
|
||||
color: <%= @styling.link_color %>;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
table,
|
||||
td,
|
||||
tr {
|
||||
vertical-align: top;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
* {
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
a[x-apple-data-detectors=true] {
|
||||
color: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
</style>
|
||||
<style id="media-query" type="text/css">
|
||||
@media (max-width: 610px) {
|
||||
|
||||
.block-grid,
|
||||
.col {
|
||||
min-width: 320px !important;
|
||||
max-width: 100% !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.block-grid {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.col {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.col>div {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.no-stack .col {
|
||||
min-width: 0 !important;
|
||||
display: table-cell !important;
|
||||
}
|
||||
|
||||
.no-stack.two-up .col {
|
||||
width: 50% !important;
|
||||
}
|
||||
|
||||
.no-stack .col.num4 {
|
||||
width: 33% !important;
|
||||
}
|
||||
|
||||
.no-stack .col.num8 {
|
||||
width: 66% !important;
|
||||
}
|
||||
|
||||
.no-stack .col.num4 {
|
||||
width: 33% !important;
|
||||
}
|
||||
|
||||
.no-stack .col.num3 {
|
||||
width: 25% !important;
|
||||
}
|
||||
|
||||
.no-stack .col.num6 {
|
||||
width: 50% !important;
|
||||
}
|
||||
|
||||
.no-stack .col.num9 {
|
||||
width: 75% !important;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="clean-body" style="margin: 0; padding: 0; -webkit-text-size-adjust: 100%; background-color: <%= @styling.background_color %>;">
|
||||
<!--[if IE]><div class="ie-browser"><![endif]-->
|
||||
<table bgcolor="<%= @styling.background_color %>" cellpadding="0" cellspacing="0" class="nl-container" role="presentation"
|
||||
style="table-layout: fixed; vertical-align: top; min-width: 320px; Margin: 0 auto; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: <%= @styling.background_color %>; width: 100%;"
|
||||
valign="top" width="100%">
|
||||
<tbody>
|
||||
<tr style="vertical-align: top;" valign="top">
|
||||
<td style="word-break: break-word; vertical-align: top;" valign="top">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td align="center" style="background-color:<%= @styling.background_color %>"><![endif]-->
|
||||
|
||||
<%# header %>
|
||||
<div style="background-color:transparent;">
|
||||
<div class="block-grid"
|
||||
style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]-->
|
||||
<div class="col num12"
|
||||
style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;">
|
||||
<div style="width:100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
<div
|
||||
style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;">
|
||||
<!--<![endif]-->
|
||||
<div align="center" class="img-container center"
|
||||
style="padding-right: 0px;padding-left: 0px;">
|
||||
<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr style="line-height:0px"><td style="padding-right: 0px;padding-left: 0px;" align="center"><![endif]--><img
|
||||
align="center" alt="Image" border="0" class="center" src="<%= @logo_url %>"
|
||||
style="text-decoration: none; -ms-interpolation-mode: bicubic; border: 0; height: 80px; width: auto; max-height: 80px; display: block;"
|
||||
title="Image" height="80" />
|
||||
<!--[if mso]></td></tr></table><![endif]-->
|
||||
</div>
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div>
|
||||
<!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td></tr></table><![endif]-->
|
||||
<!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<%# title %>
|
||||
<%= if @title do %>
|
||||
<div style="background-color:transparent;">
|
||||
<div class="block-grid"
|
||||
style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]-->
|
||||
<div class="col num12"
|
||||
style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;">
|
||||
<div style="width:100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
<div
|
||||
style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;">
|
||||
<!--<![endif]-->
|
||||
<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]-->
|
||||
<div
|
||||
style="line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;">
|
||||
<div
|
||||
style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height: 14px; color: <%= @styling.header_color %>;">
|
||||
<p style="line-height: 36px; text-align: center; margin: 0;"><span
|
||||
style="font-size: 30px; color: <%= @styling.header_color %>;"><%= @title %></span></p>
|
||||
</div>
|
||||
</div>
|
||||
<!--[if mso]></td></tr></table><![endif]-->
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div>
|
||||
<!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td></tr></table><![endif]-->
|
||||
<!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= render @view_module, @view_template, assigns %>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!--[if (IE)]></div><![endif]-->
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,5 +1,5 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do
|
||||
|
@ -69,7 +69,7 @@ defp is_status?(acct) do
|
|||
def do_follow(%{assigns: %{user: %User{} = user}} = conn, %{"user" => %{"id" => id}}) do
|
||||
with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)},
|
||||
{:ok, _, _, _} <- CommonAPI.follow(user, followee) do
|
||||
render(conn, "followed.html", %{error: false})
|
||||
redirect(conn, to: "/users/#{followee.id}")
|
||||
else
|
||||
error ->
|
||||
handle_follow_error(conn, error)
|
||||
|
@ -80,7 +80,7 @@ def do_follow(conn, %{"authorization" => %{"name" => _, "password" => _, "id" =>
|
|||
with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)},
|
||||
{_, {:ok, user}, _} <- {:auth, Authenticator.get_user(conn), followee},
|
||||
{:ok, _, _, _} <- CommonAPI.follow(user, followee) do
|
||||
render(conn, "followed.html", %{error: false})
|
||||
redirect(conn, to: "/users/#{followee.id}")
|
||||
else
|
||||
error ->
|
||||
handle_follow_error(conn, error)
|
||||
|
|
|
@ -12,4 +12,8 @@ def format_date(date) when is_binary(date) do
|
|||
|> Timex.parse!("{ISO:Extended:Z}")
|
||||
|> Timex.format!("{Mshort} {D}, {YYYY} {h24}:{m}")
|
||||
end
|
||||
|
||||
def admin_user_url(%{id: id}) do
|
||||
Pleroma.Web.Endpoint.url() <> "/pleroma/admin/#/users/" <> id
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Workers.Cron.NewUsersDigestWorker do
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
use Pleroma.Workers.WorkerHelper, queue: "new_users_digest"
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(_args, _job) do
|
||||
if Pleroma.Config.get([Pleroma.Emails.NewUsersDigestEmail, :enabled]) do
|
||||
today = NaiveDateTime.utc_now() |> Timex.beginning_of_day()
|
||||
|
||||
a_day_ago =
|
||||
today
|
||||
|> Timex.shift(days: -1)
|
||||
|> Timex.beginning_of_day()
|
||||
|
||||
users_and_statuses =
|
||||
%{
|
||||
local: true,
|
||||
order_by: :inserted_at
|
||||
}
|
||||
|> User.Query.build()
|
||||
|> where([u], u.inserted_at >= ^a_day_ago and u.inserted_at < ^today)
|
||||
|> Repo.all()
|
||||
|> Enum.map(fn user ->
|
||||
latest_status =
|
||||
Activity
|
||||
|> Activity.Queries.by_actor(user.ap_id)
|
||||
|> Activity.Queries.by_type("Create")
|
||||
|> Activity.with_preloaded_object()
|
||||
|> order_by(desc: :inserted_at)
|
||||
|> limit(1)
|
||||
|> Repo.one()
|
||||
|
||||
total_statuses =
|
||||
Activity
|
||||
|> Activity.Queries.by_actor(user.ap_id)
|
||||
|> Activity.Queries.by_type("Create")
|
||||
|> Repo.aggregate(:count, :id)
|
||||
|
||||
{user, total_statuses, latest_status}
|
||||
end)
|
||||
|
||||
if users_and_statuses != [] do
|
||||
%{is_admin: true}
|
||||
|> User.Query.build()
|
||||
|> Repo.all()
|
||||
|> Enum.map(&Pleroma.Emails.NewUsersDigestEmail.new_users(&1, users_and_statuses))
|
||||
|> Enum.each(&Pleroma.Emails.Mailer.deliver/1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Workers.RemoteFetcherWorker do
|
||||
alias Pleroma.Object.Fetcher
|
||||
|
||||
use Pleroma.Workers.WorkerHelper, queue: "remote_fetcher"
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(
|
||||
%{
|
||||
"op" => "fetch_remote",
|
||||
"id" => id
|
||||
} = args,
|
||||
_job
|
||||
) do
|
||||
{:ok, _object} = Fetcher.fetch_object_from_id(id, depth: args["depth"])
|
||||
end
|
||||
end
|
2
mix.exs
2
mix.exs
|
@ -139,8 +139,8 @@ defp deps do
|
|||
{:phoenix_swoosh, "~> 0.2"},
|
||||
{:gen_smtp, "~> 0.13"},
|
||||
{:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test},
|
||||
{:floki, "~> 0.23.0"},
|
||||
{:ex_syslogger, "~> 1.4"},
|
||||
{:floki, "~> 0.25"},
|
||||
{:timex, "~> 3.5"},
|
||||
{:ueberauth, "~> 0.4"},
|
||||
{:auto_linker,
|
||||
|
|
4
mix.lock
4
mix.lock
|
@ -40,13 +40,13 @@
|
|||
"fast_html": {:hex, :fast_html, "1.0.1", "5bc7df4dc4607ec2c314c16414e4111d79a209956c4f5df96602d194c61197f9", [:make, :mix], [], "hexpm", "18e627dd62051a375ef94b197f41e8027c3e8eef0180ab8f81e0543b3dc6900a"},
|
||||
"fast_sanitize": {:hex, :fast_sanitize, "0.1.6", "60a5ae96879956dea409a91a77f5dd2994c24cc10f80eefd8f9892ee4c0c7b25", [:mix], [{:fast_html, "~> 1.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b73f50f0cb522dd0331ea8e8c90b408de42c50f37641219d6364f0e3e7efd22c"},
|
||||
"flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"},
|
||||
"floki": {:hex, :floki, "0.23.1", "e100306ce7d8841d70a559748e5091542e2cfc67ffb3ade92b89a8435034dab1", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "39b431b6330206cadee418e793177401ebedf2e86abc945ddd545aedb37dfc19"},
|
||||
"floki": {:hex, :floki, "0.26.0", "4df88977e2e357c6720e1b650f613444bfb48c5acfc6a0c646ab007d08ad13bf", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "e7b66ce7feef5518a9cd9fc7b52dd62a64028bd9cb6d6ad282a0f0fc90a4ae52"},
|
||||
"gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"},
|
||||
"gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm", "8453e2289d94c3199396eb517d65d6715ef26bcae0ee83eb5ff7a84445458d76"},
|
||||
"gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm", "5cacd405e72b2609a7e1f891bddb80c53d0b3b7b0036d1648e7382ca108c41c8"},
|
||||
"gettext": {:hex, :gettext, "0.17.1", "8baab33482df4907b3eae22f719da492cee3981a26e649b9c2be1c0192616962", [:mix], [], "hexpm", "f7d97341e536f95b96eef2988d6d4230f7262cf239cda0e2e63123ee0b717222"},
|
||||
"hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"},
|
||||
"html_entities": {:hex, :html_entities, "0.5.0", "40f5c5b9cbe23073b48a4e69c67b6c11974f623a76165e2b92d098c0e88ccb1d", [:mix], [], "hexpm", "8e9186e1873bea1067895f6a542b59df6c9fcf3b516ba272eeff3ea0c7b755cd"},
|
||||
"html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"},
|
||||
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "293d77bb6f4a67ac8bde1428735c3b42f22cbb30", [ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"]},
|
||||
"httpoison": {:hex, :httpoison, "1.6.1", "2ce5bf6e535cd0ab02e905ba8c276580bab80052c5c549f53ddea52d72e81f33", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "89149056039084024a284cd703b2d1900d584958dba432132cb21ef35aed7487"},
|
||||
|
|
|
@ -17,7 +17,11 @@ def up do
|
|||
Repo.stream(query)
|
||||
|> Enum.each(fn %{id: user_id, bookmarks: bookmarks} ->
|
||||
Enum.each(bookmarks, fn ap_id ->
|
||||
activity = Activity.get_create_by_object_ap_id(ap_id)
|
||||
activity =
|
||||
ap_id
|
||||
|> Activity.create_by_object_ap_id()
|
||||
|> Repo.one()
|
||||
|
||||
unless is_nil(activity), do: {:ok, _} = Bookmark.create(user_id, activity.id)
|
||||
end)
|
||||
end)
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
defmodule Pleroma.Repo.Migrations.AddFollowingAddressFromSourceData do
|
||||
use Ecto.Migration
|
||||
import Ecto.Query
|
||||
alias Pleroma.User
|
||||
import Ecto.Query
|
||||
require Logger
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
query =
|
||||
|
@ -19,6 +20,9 @@ def change do
|
|||
:following_address
|
||||
])
|
||||
|> Pleroma.Repo.update()
|
||||
|
||||
user ->
|
||||
Logger.warn("User #{user.id} / #{user.nickname} does not seem to have source_data")
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,8 @@ defmodule Pleroma.Repo.Migrations.CopyMutedToMutedNotifications do
|
|||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
execute("update users set info = '{}' where info is null")
|
||||
|
||||
execute(
|
||||
"update users set info = safe_jsonb_set(info, '{muted_notifications}', info->'mutes', true) where local = true"
|
||||
)
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
defmodule Pleroma.Repo.Migrations.AddCounterCacheTable do
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
create_if_not_exists table(:counter_cache) do
|
||||
add(:name, :string, null: false)
|
||||
add(:count, :bigint, null: false, default: 0)
|
||||
end
|
||||
|
||||
create_if_not_exists(unique_index(:counter_cache, [:name]))
|
||||
|
||||
"""
|
||||
CREATE OR REPLACE FUNCTION update_status_visibility_counter_cache()
|
||||
RETURNS TRIGGER AS
|
||||
$$
|
||||
DECLARE
|
||||
BEGIN
|
||||
IF TG_OP = 'INSERT' THEN
|
||||
IF NEW.data->>'type' = 'Create' THEN
|
||||
EXECUTE 'INSERT INTO counter_cache (name, count) VALUES (''status_visibility_' || activity_visibility(NEW.actor, NEW.recipients, NEW.data) || ''', 1) ON CONFLICT (name) DO UPDATE SET count = counter_cache.count + 1';
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
ELSIF TG_OP = 'UPDATE' THEN
|
||||
IF (NEW.data->>'type' = 'Create') and (OLD.data->>'type' = 'Create') and activity_visibility(NEW.actor, NEW.recipients, NEW.data) != activity_visibility(OLD.actor, OLD.recipients, OLD.data) THEN
|
||||
EXECUTE 'INSERT INTO counter_cache (name, count) VALUES (''status_visibility_' || activity_visibility(NEW.actor, NEW.recipients, NEW.data) || ''', 1) ON CONFLICT (name) DO UPDATE SET count = counter_cache.count + 1';
|
||||
EXECUTE 'update counter_cache SET count = counter_cache.count - 1 where count > 0 and name = ''status_visibility_' || activity_visibility(OLD.actor, OLD.recipients, OLD.data) || ''';';
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
ELSIF TG_OP = 'DELETE' THEN
|
||||
IF OLD.data->>'type' = 'Create' THEN
|
||||
EXECUTE 'update counter_cache SET count = counter_cache.count - 1 where count > 0 and name = ''status_visibility_' || activity_visibility(OLD.actor, OLD.recipients, OLD.data) || ''';';
|
||||
END IF;
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE 'plpgsql';
|
||||
"""
|
||||
|> execute()
|
||||
|
||||
"""
|
||||
CREATE TRIGGER status_visibility_counter_cache_trigger BEFORE INSERT OR UPDATE of recipients, data OR DELETE ON activities
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE update_status_visibility_counter_cache();
|
||||
"""
|
||||
|> execute()
|
||||
end
|
||||
|
||||
def down do
|
||||
execute("drop trigger if exists status_visibility_counter_cache_trigger on activities")
|
||||
execute("drop function if exists update_status_visibility_counter_cache()")
|
||||
drop_if_exists(unique_index(:counter_cache, [:name]))
|
||||
drop_if_exists(table(:counter_cache))
|
||||
end
|
||||
end
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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.1abbc9b8.css rel=stylesheet><link href=chunk-libs.686b5876.css rel=stylesheet><link href=app.c836e084.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=static/js/runtime.8d23a4f7.js></script><script type=text/javascript src=static/js/chunk-elementUI.fba0efec.js></script><script type=text/javascript src=static/js/chunk-libs.b8c453ab.js></script><script type=text/javascript src=static/js/app.a753ced5.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.1abbc9b8.css rel=stylesheet><link href=chunk-libs.686b5876.css rel=stylesheet><link href=app.c836e084.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=static/js/runtime.dfdeb6eb.js></script><script type=text/javascript src=static/js/chunk-elementUI.fba0efec.js></script><script type=text/javascript src=static/js/chunk-libs.b8c453ab.js></script><script type=text/javascript src=static/js/app.5f0094e3.js></script></body></html>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,26 +1,72 @@
|
|||
defmodule Restarter.Pleroma do
|
||||
use GenServer
|
||||
|
||||
require Logger
|
||||
|
||||
def start_link(_) do
|
||||
GenServer.start_link(__MODULE__, [], name: __MODULE__)
|
||||
end
|
||||
|
||||
def init(_), do: {:ok, %{}}
|
||||
def init(_), do: {:ok, %{need_reboot?: false}}
|
||||
|
||||
def handle_info(:after_boot, %{after_boot: true} = state), do: {:noreply, state}
|
||||
def need_reboot? do
|
||||
GenServer.call(__MODULE__, :need_reboot?)
|
||||
end
|
||||
|
||||
def handle_info(:after_boot, state) do
|
||||
restart(:pleroma)
|
||||
def need_reboot do
|
||||
GenServer.cast(__MODULE__, :need_reboot)
|
||||
end
|
||||
|
||||
def refresh do
|
||||
GenServer.cast(__MODULE__, :refresh)
|
||||
end
|
||||
|
||||
def restart(env, delay) do
|
||||
GenServer.cast(__MODULE__, {:restart, env, delay})
|
||||
end
|
||||
|
||||
def restart_after_boot(env) do
|
||||
GenServer.cast(__MODULE__, {:after_boot, env})
|
||||
end
|
||||
|
||||
def handle_call(:need_reboot?, _from, state) do
|
||||
{:reply, state[:need_reboot?], state}
|
||||
end
|
||||
|
||||
def handle_cast(:refresh, _state) do
|
||||
{:noreply, %{need_reboot?: false}}
|
||||
end
|
||||
|
||||
def handle_cast(:need_reboot, %{need_reboot?: true} = state), do: {:noreply, state}
|
||||
|
||||
def handle_cast(:need_reboot, state) do
|
||||
{:noreply, Map.put(state, :need_reboot?, true)}
|
||||
end
|
||||
|
||||
def handle_cast({:restart, :test, _}, state) do
|
||||
Logger.warn("pleroma restarted")
|
||||
{:noreply, Map.put(state, :need_reboot?, false)}
|
||||
end
|
||||
|
||||
def handle_cast({:restart, _, delay}, state) do
|
||||
Process.sleep(delay)
|
||||
do_restart(:pleroma)
|
||||
{:noreply, Map.put(state, :need_reboot?, false)}
|
||||
end
|
||||
|
||||
def handle_cast({:after_boot, _}, %{after_boot: true} = state), do: {:noreply, state}
|
||||
|
||||
def handle_cast({:after_boot, :test}, state) do
|
||||
Logger.warn("pleroma restarted")
|
||||
{:noreply, Map.put(state, :after_boot, true)}
|
||||
end
|
||||
|
||||
def handle_info({:restart, delay}, state) do
|
||||
Process.sleep(delay)
|
||||
restart(:pleroma)
|
||||
{:noreply, state}
|
||||
def handle_cast({:after_boot, _}, state) do
|
||||
do_restart(:pleroma)
|
||||
{:noreply, Map.put(state, :after_boot, true)}
|
||||
end
|
||||
|
||||
defp restart(app) do
|
||||
defp do_restart(app) do
|
||||
:ok = Application.ensure_started(app)
|
||||
:ok = Application.stop(app)
|
||||
:ok = Application.start(app)
|
||||
|
|
|
@ -138,6 +138,8 @@ test "when association is not loaded" do
|
|||
}
|
||||
end
|
||||
|
||||
clear_config([:instance, :limit_to_local_content])
|
||||
|
||||
test "finds utf8 text in statuses", %{
|
||||
japanese_activity: japanese_activity,
|
||||
user: user
|
||||
|
@ -165,7 +167,6 @@ test "find only local statuses for unauthenticated users when `limit_to_local_c
|
|||
%{local_activity: local_activity} do
|
||||
Pleroma.Config.put([:instance, :limit_to_local_content], :all)
|
||||
assert [^local_activity] = Activity.search(nil, "find me")
|
||||
Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated)
|
||||
end
|
||||
|
||||
test "find all statuses for unauthenticated users when `limit_to_local_content` is `false`",
|
||||
|
@ -178,8 +179,6 @@ test "find all statuses for unauthenticated users when `limit_to_local_content`
|
|||
activities = Enum.sort_by(Activity.search(nil, "find me"), & &1.id)
|
||||
|
||||
assert [^local_activity, ^remote_activity] = activities
|
||||
|
||||
Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,17 +1,20 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.CaptchaTest do
|
||||
use ExUnit.Case
|
||||
use Pleroma.DataCase
|
||||
|
||||
import Tesla.Mock
|
||||
|
||||
alias Pleroma.Captcha
|
||||
alias Pleroma.Captcha.Kocaptcha
|
||||
alias Pleroma.Captcha.Native
|
||||
|
||||
@ets_options [:ordered_set, :private, :named_table, {:read_concurrency, true}]
|
||||
|
||||
clear_config([Pleroma.Captcha, :enabled])
|
||||
|
||||
describe "Kocaptcha" do
|
||||
setup do
|
||||
ets_name = Kocaptcha.Ets
|
||||
|
@ -31,17 +34,18 @@ defmodule Pleroma.CaptchaTest do
|
|||
|
||||
test "new and validate" do
|
||||
new = Kocaptcha.new()
|
||||
assert new[:type] == :kocaptcha
|
||||
assert new[:token] == "afa1815e14e29355e6c8f6b143a39fa2"
|
||||
|
||||
assert new[:url] ==
|
||||
"https://captcha.kotobank.ch/captchas/afa1815e14e29355e6c8f6b143a39fa2.png"
|
||||
token = "afa1815e14e29355e6c8f6b143a39fa2"
|
||||
url = "https://captcha.kotobank.ch/captchas/afa1815e14e29355e6c8f6b143a39fa2.png"
|
||||
|
||||
assert Kocaptcha.validate(
|
||||
new[:token],
|
||||
"7oEy8c",
|
||||
new[:answer_data]
|
||||
) == :ok
|
||||
assert %{
|
||||
answer_data: answer,
|
||||
token: ^token,
|
||||
url: ^url,
|
||||
type: :kocaptcha
|
||||
} = new
|
||||
|
||||
assert Kocaptcha.validate(token, "7oEy8c", answer) == :ok
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -61,4 +65,52 @@ test "new and validate" do
|
|||
assert {:error, "Invalid CAPTCHA"} == Native.validate(token, answer, answer <> "foobar")
|
||||
end
|
||||
end
|
||||
|
||||
describe "Captcha Wrapper" do
|
||||
test "validate" do
|
||||
Pleroma.Config.put([Pleroma.Captcha, :enabled], true)
|
||||
|
||||
new = Captcha.new()
|
||||
|
||||
assert %{
|
||||
answer_data: answer,
|
||||
token: token
|
||||
} = new
|
||||
|
||||
assert is_binary(answer)
|
||||
assert :ok = Captcha.validate(token, "63615261b77f5354fb8c4e4986477555", answer)
|
||||
end
|
||||
|
||||
test "doesn't validate invalid answer" do
|
||||
Pleroma.Config.put([Pleroma.Captcha, :enabled], true)
|
||||
|
||||
new = Captcha.new()
|
||||
|
||||
assert %{
|
||||
answer_data: answer,
|
||||
token: token
|
||||
} = new
|
||||
|
||||
assert is_binary(answer)
|
||||
|
||||
assert {:error, "Invalid answer data"} =
|
||||
Captcha.validate(token, "63615261b77f5354fb8c4e4986477555", answer <> "foobar")
|
||||
end
|
||||
|
||||
test "nil answer_data" do
|
||||
Pleroma.Config.put([Pleroma.Captcha, :enabled], true)
|
||||
|
||||
new = Captcha.new()
|
||||
|
||||
assert %{
|
||||
answer_data: answer,
|
||||
token: token
|
||||
} = new
|
||||
|
||||
assert is_binary(answer)
|
||||
|
||||
assert {:error, "Invalid answer data"} =
|
||||
Captcha.validate(token, "63615261b77f5354fb8c4e4986477555", nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -109,6 +109,10 @@ test "transfer config values with full subkey update" do
|
|||
end
|
||||
|
||||
describe "pleroma restart" do
|
||||
setup do
|
||||
on_exit(fn -> Restarter.Pleroma.refresh() end)
|
||||
end
|
||||
|
||||
test "don't restart if no reboot time settings were changed" do
|
||||
emoji = Application.get_env(:pleroma, :emoji)
|
||||
on_exit(fn -> Application.put_env(:pleroma, :emoji, emoji) end)
|
||||
|
@ -125,7 +129,7 @@ test "don't restart if no reboot time settings were changed" do
|
|||
)
|
||||
end
|
||||
|
||||
test "restart pleroma on reboot time key" do
|
||||
test "on reboot time key" do
|
||||
chat = Application.get_env(:pleroma, :chat)
|
||||
on_exit(fn -> Application.put_env(:pleroma, :chat, chat) end)
|
||||
|
||||
|
@ -138,7 +142,7 @@ test "restart pleroma on reboot time key" do
|
|||
assert capture_log(fn -> TransferTask.start_link([]) end) =~ "pleroma restarted"
|
||||
end
|
||||
|
||||
test "restart pleroma on reboot time subkey" do
|
||||
test "on reboot time subkey" do
|
||||
captcha = Application.get_env(:pleroma, Pleroma.Captcha)
|
||||
on_exit(fn -> Application.put_env(:pleroma, Pleroma.Captcha, captcha) end)
|
||||
|
||||
|
|
|
@ -35,6 +35,19 @@
|
|||
"inReplyTo": null,
|
||||
"inReplyToAtomUri": null,
|
||||
"published": "2018-02-12T14:08:20Z",
|
||||
"replies": {
|
||||
"id": "http://mastodon.example.org/users/admin/statuses/99512778738411822/replies",
|
||||
"type": "Collection",
|
||||
"first": {
|
||||
"type": "CollectionPage",
|
||||
"next": "http://mastodon.example.org/users/admin/statuses/99512778738411822/replies?min_id=99512778738411824&page=true",
|
||||
"partOf": "http://mastodon.example.org/users/admin/statuses/99512778738411822/replies",
|
||||
"items": [
|
||||
"http://mastodon.example.org/users/admin/statuses/99512778738411823",
|
||||
"http://mastodon.example.org/users/admin/statuses/99512778738411824"
|
||||
]
|
||||
}
|
||||
},
|
||||
"sensitive": true,
|
||||
"summary": "cw",
|
||||
"tag": [
|
||||
|
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.HTTP.RequestBuilderTest do
|
|||
|
||||
describe "headers/2" do
|
||||
clear_config([:http, :send_user_agent])
|
||||
clear_config([:http, :user_agent])
|
||||
|
||||
test "don't send pleroma user agent" do
|
||||
assert RequestBuilder.headers(%{}, []) == %{headers: []}
|
||||
|
|
|
@ -26,6 +26,31 @@ defmodule Pleroma.Object.FetcherTest do
|
|||
:ok
|
||||
end
|
||||
|
||||
describe "max thread distance restriction" do
|
||||
@ap_id "http://mastodon.example.org/@admin/99541947525187367"
|
||||
|
||||
clear_config([:instance, :federation_incoming_replies_max_depth])
|
||||
|
||||
test "it returns thread depth exceeded error if thread depth is exceeded" do
|
||||
Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
|
||||
|
||||
assert {:error, "Max thread distance exceeded."} =
|
||||
Fetcher.fetch_object_from_id(@ap_id, depth: 1)
|
||||
end
|
||||
|
||||
test "it fetches object if max thread depth is restricted to 0 and depth is not specified" do
|
||||
Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
|
||||
|
||||
assert {:ok, _} = Fetcher.fetch_object_from_id(@ap_id)
|
||||
end
|
||||
|
||||
test "it fetches object if requested depth does not exceed max thread depth" do
|
||||
Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 10)
|
||||
|
||||
assert {:ok, _} = Fetcher.fetch_object_from_id(@ap_id, depth: 10)
|
||||
end
|
||||
end
|
||||
|
||||
describe "actor origin containment" do
|
||||
test "it rejects objects with a bogus origin" do
|
||||
{:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity.json")
|
||||
|
|
|
@ -75,6 +75,7 @@ test "ensures cache is cleared for the object" do
|
|||
|
||||
describe "delete attachments" do
|
||||
clear_config([Pleroma.Upload])
|
||||
clear_config([:instance, :cleanup_attachments])
|
||||
|
||||
test "Disabled via config" do
|
||||
Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
|
||||
|
|
|
@ -23,6 +23,8 @@ test "does nothing if a user is assigned", %{conn: conn} do
|
|||
end
|
||||
|
||||
describe "when secret set it assigns an admin user" do
|
||||
clear_config([:admin_token])
|
||||
|
||||
test "with `admin_token` query parameter", %{conn: conn} do
|
||||
Pleroma.Config.put(:admin_token, "password123")
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do
|
|||
|
||||
clear_config([:http_securiy, :enabled])
|
||||
clear_config([:http_security, :sts])
|
||||
clear_config([:http_security, :referrer_policy])
|
||||
|
||||
describe "http security enabled" do
|
||||
setup do
|
||||
|
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
|
|||
alias Pleroma.Web.Plugs.HTTPSignaturePlug
|
||||
|
||||
import Plug.Conn
|
||||
import Phoenix.Controller, only: [put_format: 2]
|
||||
import Mock
|
||||
|
||||
test "it call HTTPSignatures to check validity if the actor sighed it" do
|
||||
|
@ -20,10 +21,69 @@ test "it call HTTPSignatures to check validity if the actor sighed it" do
|
|||
"signature",
|
||||
"keyId=\"http://mastodon.example.org/users/admin#main-key"
|
||||
)
|
||||
|> put_format("activity+json")
|
||||
|> HTTPSignaturePlug.call(%{})
|
||||
|
||||
assert conn.assigns.valid_signature == true
|
||||
assert conn.halted == false
|
||||
assert called(HTTPSignatures.validate_conn(:_))
|
||||
end
|
||||
end
|
||||
|
||||
describe "requires a signature when `authorized_fetch_mode` is enabled" do
|
||||
setup do
|
||||
Pleroma.Config.put([:activitypub, :authorized_fetch_mode], true)
|
||||
|
||||
on_exit(fn ->
|
||||
Pleroma.Config.put([:activitypub, :authorized_fetch_mode], false)
|
||||
end)
|
||||
|
||||
params = %{"actor" => "http://mastodon.example.org/users/admin"}
|
||||
conn = build_conn(:get, "/doesntmattter", params) |> put_format("activity+json")
|
||||
|
||||
[conn: conn]
|
||||
end
|
||||
|
||||
test "when signature header is present", %{conn: conn} do
|
||||
with_mock HTTPSignatures, validate_conn: fn _ -> false end do
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header(
|
||||
"signature",
|
||||
"keyId=\"http://mastodon.example.org/users/admin#main-key"
|
||||
)
|
||||
|> HTTPSignaturePlug.call(%{})
|
||||
|
||||
assert conn.assigns.valid_signature == false
|
||||
assert conn.halted == true
|
||||
assert conn.status == 401
|
||||
assert conn.state == :sent
|
||||
assert conn.resp_body == "Request not signed"
|
||||
assert called(HTTPSignatures.validate_conn(:_))
|
||||
end
|
||||
|
||||
with_mock HTTPSignatures, validate_conn: fn _ -> true end do
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header(
|
||||
"signature",
|
||||
"keyId=\"http://mastodon.example.org/users/admin#main-key"
|
||||
)
|
||||
|> HTTPSignaturePlug.call(%{})
|
||||
|
||||
assert conn.assigns.valid_signature == true
|
||||
assert conn.halted == false
|
||||
assert called(HTTPSignatures.validate_conn(:_))
|
||||
end
|
||||
end
|
||||
|
||||
test "halts the connection when `signature` header is not present", %{conn: conn} do
|
||||
conn = HTTPSignaturePlug.call(conn, %{})
|
||||
assert conn.assigns[:valid_signature] == nil
|
||||
assert conn.halted == true
|
||||
assert conn.status == 401
|
||||
assert conn.state == :sent
|
||||
assert conn.resp_body == "Request not signed"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,6 +8,10 @@ defmodule Pleroma.Plugs.RemoteIpTest do
|
|||
|
||||
alias Pleroma.Plugs.RemoteIp
|
||||
|
||||
import Pleroma.Tests.Helpers, only: [clear_config: 1, clear_config: 2]
|
||||
|
||||
clear_config(RemoteIp)
|
||||
|
||||
test "disabled" do
|
||||
Pleroma.Config.put(RemoteIp, enabled: false)
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ defmodule Pleroma.Plugs.UserEnabledPlugTest do
|
|||
alias Pleroma.Plugs.UserEnabledPlug
|
||||
import Pleroma.Factory
|
||||
|
||||
clear_config([:instance, :account_activation_required])
|
||||
|
||||
test "doesn't do anything if the user isn't set", %{conn: conn} do
|
||||
ret_conn =
|
||||
conn
|
||||
|
@ -18,7 +20,6 @@ test "doesn't do anything if the user isn't set", %{conn: conn} do
|
|||
|
||||
test "with a user that's not confirmed and a config requiring confirmation, it removes that user",
|
||||
%{conn: conn} do
|
||||
old = Pleroma.Config.get([:instance, :account_activation_required])
|
||||
Pleroma.Config.put([:instance, :account_activation_required], true)
|
||||
|
||||
user = insert(:user, confirmation_pending: true)
|
||||
|
@ -29,8 +30,6 @@ test "with a user that's not confirmed and a config requiring confirmation, it r
|
|||
|> UserEnabledPlug.call(%{})
|
||||
|
||||
assert conn.assigns.user == nil
|
||||
|
||||
Pleroma.Config.put([:instance, :account_activation_required], old)
|
||||
end
|
||||
|
||||
test "with a user that is deactivated, it removes that user", %{conn: conn} do
|
||||
|
|
|
@ -67,6 +67,8 @@ test "return error if has not assoc " do
|
|||
:ok
|
||||
end
|
||||
|
||||
clear_config([:i_am_aware_this_may_cause_data_loss, :disable_migration_check])
|
||||
|
||||
test "raises if it detects unapplied migrations" do
|
||||
assert_raise Pleroma.Repo.UnappliedMigrationsError, fn ->
|
||||
capture_log(&Repo.check_migrations_applied!/0)
|
||||
|
@ -74,18 +76,8 @@ test "raises if it detects unapplied migrations" do
|
|||
end
|
||||
|
||||
test "doesn't do anything if disabled" do
|
||||
disable_migration_check =
|
||||
Pleroma.Config.get([:i_am_aware_this_may_cause_data_loss, :disable_migration_check])
|
||||
|
||||
Pleroma.Config.put([:i_am_aware_this_may_cause_data_loss, :disable_migration_check], true)
|
||||
|
||||
on_exit(fn ->
|
||||
Pleroma.Config.put(
|
||||
[:i_am_aware_this_may_cause_data_loss, :disable_migration_check],
|
||||
disable_migration_check
|
||||
)
|
||||
end)
|
||||
|
||||
assert :ok == Repo.check_migrations_applied!()
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.StateTest do
|
||||
use Pleroma.DataCase
|
||||
import Pleroma.Factory
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
describe "status visibility count" do
|
||||
test "on new status" do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
CommonAPI.post(user, %{"visibility" => "public", "status" => "hey"})
|
||||
|
||||
Enum.each(0..1, fn _ ->
|
||||
CommonAPI.post(user, %{
|
||||
"visibility" => "unlisted",
|
||||
"status" => "hey"
|
||||
})
|
||||
end)
|
||||
|
||||
Enum.each(0..2, fn _ ->
|
||||
CommonAPI.post(user, %{
|
||||
"visibility" => "direct",
|
||||
"status" => "hey @#{other_user.nickname}"
|
||||
})
|
||||
end)
|
||||
|
||||
Enum.each(0..3, fn _ ->
|
||||
CommonAPI.post(user, %{
|
||||
"visibility" => "private",
|
||||
"status" => "hey"
|
||||
})
|
||||
end)
|
||||
|
||||
assert %{direct: 3, private: 4, public: 1, unlisted: 2} =
|
||||
Pleroma.Stats.get_status_visibility_count()
|
||||
end
|
||||
|
||||
test "on status delete" do
|
||||
user = insert(:user)
|
||||
{:ok, activity} = CommonAPI.post(user, %{"visibility" => "public", "status" => "hey"})
|
||||
assert %{public: 1} = Pleroma.Stats.get_status_visibility_count()
|
||||
CommonAPI.delete(activity.id, user)
|
||||
assert %{public: 0} = Pleroma.Stats.get_status_visibility_count()
|
||||
end
|
||||
|
||||
test "on status visibility update" do
|
||||
user = insert(:user)
|
||||
{:ok, activity} = CommonAPI.post(user, %{"visibility" => "public", "status" => "hey"})
|
||||
assert %{public: 1, private: 0} = Pleroma.Stats.get_status_visibility_count()
|
||||
{:ok, _} = CommonAPI.update_activity_scope(activity.id, %{"visibility" => "private"})
|
||||
assert %{public: 0, private: 1} = Pleroma.Stats.get_status_visibility_count()
|
||||
end
|
||||
|
||||
test "doesn't count unrelated activities" do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
{:ok, activity} = CommonAPI.post(user, %{"visibility" => "public", "status" => "hey"})
|
||||
_ = CommonAPI.follow(user, other_user)
|
||||
CommonAPI.favorite(activity.id, other_user)
|
||||
CommonAPI.repeat(activity.id, other_user)
|
||||
|
||||
assert %{direct: 0, private: 0, public: 1, unlisted: 0} =
|
||||
Pleroma.Stats.get_status_visibility_count()
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,5 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Captcha.Mock do
|
||||
|
@ -7,8 +7,17 @@ defmodule Pleroma.Captcha.Mock do
|
|||
@behaviour Service
|
||||
|
||||
@impl Service
|
||||
def new, do: %{type: :mock}
|
||||
def new,
|
||||
do: %{
|
||||
type: :mock,
|
||||
token: "afa1815e14e29355e6c8f6b143a39fa2",
|
||||
answer_data: "63615261b77f5354fb8c4e4986477555",
|
||||
url: "https://example.org/captcha.png"
|
||||
}
|
||||
|
||||
@impl Service
|
||||
def validate(_token, _captcha, _data), do: :ok
|
||||
def validate(_token, captcha, captcha) when not is_nil(captcha), do: :ok
|
||||
|
||||
def validate(_token, captcha, answer),
|
||||
do: {:error, "Invalid CAPTCHA captcha: #{inspect(captcha)} ; answer: #{inspect(answer)}"}
|
||||
end
|
||||
|
|
|
@ -26,6 +26,7 @@ defmacro clear_config(config_path, do: yield) do
|
|||
end
|
||||
end
|
||||
|
||||
@doc "Stores initial config value and restores it after *all* test examples are executed."
|
||||
defmacro clear_config_all(config_path) do
|
||||
quote do
|
||||
clear_config_all(unquote(config_path)) do
|
||||
|
@ -33,6 +34,11 @@ defmacro clear_config_all(config_path) do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Stores initial config value and restores it after *all* test examples are executed.
|
||||
Only use if *all* test examples should work with the same stubbed value
|
||||
(*no* examples set a different value).
|
||||
"""
|
||||
defmacro clear_config_all(config_path, do: yield) do
|
||||
quote do
|
||||
setup_all do
|
||||
|
|
|
@ -9,6 +9,10 @@ defmodule Pleroma.Tests.ObanHelpers do
|
|||
|
||||
alias Pleroma.Repo
|
||||
|
||||
def wipe_all do
|
||||
Repo.delete_all(Oban.Job)
|
||||
end
|
||||
|
||||
def perform_all do
|
||||
Oban.Job
|
||||
|> Repo.all()
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.InstanceTest do
|
||||
use ExUnit.Case, async: true
|
||||
use ExUnit.Case
|
||||
|
||||
setup do
|
||||
File.mkdir_p!(tmp_path())
|
||||
|
@ -15,6 +15,8 @@ defmodule Pleroma.InstanceTest do
|
|||
if File.exists?(static_dir) do
|
||||
File.rm_rf(Path.join(static_dir, "robots.txt"))
|
||||
end
|
||||
|
||||
Pleroma.Config.put([:instance, :static_dir], static_dir)
|
||||
end)
|
||||
|
||||
:ok
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mix.Tasks.Pleroma.RefreshCounterCacheTest do
|
||||
use Pleroma.DataCase
|
||||
alias Pleroma.Web.CommonAPI
|
||||
import ExUnit.CaptureIO, only: [capture_io: 1]
|
||||
import Pleroma.Factory
|
||||
|
||||
test "counts statuses" do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
CommonAPI.post(user, %{"visibility" => "public", "status" => "hey"})
|
||||
|
||||
Enum.each(0..1, fn _ ->
|
||||
CommonAPI.post(user, %{
|
||||
"visibility" => "unlisted",
|
||||
"status" => "hey"
|
||||
})
|
||||
end)
|
||||
|
||||
Enum.each(0..2, fn _ ->
|
||||
CommonAPI.post(user, %{
|
||||
"visibility" => "direct",
|
||||
"status" => "hey @#{other_user.nickname}"
|
||||
})
|
||||
end)
|
||||
|
||||
Enum.each(0..3, fn _ ->
|
||||
CommonAPI.post(user, %{
|
||||
"visibility" => "private",
|
||||
"status" => "hey"
|
||||
})
|
||||
end)
|
||||
|
||||
assert capture_io(fn -> Mix.Tasks.Pleroma.RefreshCounterCache.run([]) end) =~ "Done\n"
|
||||
|
||||
assert %{direct: 3, private: 4, public: 1, unlisted: 2} =
|
||||
Pleroma.Stats.get_status_visibility_count()
|
||||
end
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue