Merge branch 'develop' into issue/1276
This commit is contained in:
commit
b87533760b
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -9,11 +9,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- **Breaking**: OStatus protocol support
|
- **Breaking**: OStatus protocol support
|
||||||
- **Breaking**: MDII uploader
|
- **Breaking**: MDII uploader
|
||||||
- **Breaking**: Using third party engines for user recommendation
|
- **Breaking**: Using third party engines for user recommendation
|
||||||
|
<details>
|
||||||
|
<summary>API Changes</summary>
|
||||||
|
- **Breaking**: AdminAPI: migrate_from_db endpoint
|
||||||
|
</details>
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- **Breaking:** Pleroma won't start if it detects unapplied migrations
|
- **Breaking:** Pleroma won't start if it detects unapplied migrations
|
||||||
- **Breaking:** attachments are removed along with statuses when there are no other references to it
|
|
||||||
- **Breaking:** Elixir >=1.8 is now required (was >= 1.7)
|
- **Breaking:** Elixir >=1.8 is now required (was >= 1.7)
|
||||||
|
- **Breaking:** `Pleroma.Plugs.RemoteIp` and `:rate_limiter` enabled by default. Please ensure your reverse proxy forwards the real IP!
|
||||||
- **Breaking:** attachment links (`config :pleroma, :instance, no_attachment_links` and `config :pleroma, Pleroma.Upload, link_name`) disabled by default
|
- **Breaking:** attachment links (`config :pleroma, :instance, no_attachment_links` and `config :pleroma, Pleroma.Upload, link_name`) disabled by default
|
||||||
- **Breaking:** OAuth: defaulted `[:auth, :enforce_oauth_admin_scope_usage]` setting to `true` which demands `admin` OAuth scope to perform admin actions (in addition to `is_admin` flag on User); make sure to use bundled or newer versions of AdminFE & PleromaFE to access admin / moderator features.
|
- **Breaking:** OAuth: defaulted `[:auth, :enforce_oauth_admin_scope_usage]` setting to `true` which demands `admin` OAuth scope to perform admin actions (in addition to `is_admin` flag on User); make sure to use bundled or newer versions of AdminFE & PleromaFE to access admin / moderator features.
|
||||||
- **Breaking:** Dynamic configuration has been rearchitected. The `:pleroma, :instance, dynamic_configuration` setting has been replaced with `config :pleroma, configurable_from_database`. Please backup your configuration to a file and run the migration task to ensure consistency with the new schema.
|
- **Breaking:** Dynamic configuration has been rearchitected. The `:pleroma, :instance, dynamic_configuration` setting has been replaced with `config :pleroma, configurable_from_database`. Please backup your configuration to a file and run the migration task to ensure consistency with the new schema.
|
||||||
|
@ -27,6 +31,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Deprecated `User.Info` embedded schema (fields moved to `User`)
|
- Deprecated `User.Info` embedded schema (fields moved to `User`)
|
||||||
- Store status data inside Flag activity
|
- Store status data inside Flag activity
|
||||||
- Deprecated (reorganized as `UserRelationship` entity) User fields with user AP IDs (`blocks`, `mutes`, `muted_reblogs`, `muted_notifications`, `subscribers`).
|
- Deprecated (reorganized as `UserRelationship` entity) User fields with user AP IDs (`blocks`, `mutes`, `muted_reblogs`, `muted_notifications`, `subscribers`).
|
||||||
|
- Rate limiter is now disabled for localhost/socket (unless remoteip plug is enabled)
|
||||||
- Logger: default log level changed from `warn` to `info`.
|
- Logger: default log level changed from `warn` to `info`.
|
||||||
- Config mix task `migrate_to_db` truncates `config` table before migrating the config file.
|
- Config mix task `migrate_to_db` truncates `config` table before migrating the config file.
|
||||||
<details>
|
<details>
|
||||||
|
@ -37,6 +42,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- **Breaking:** Admin API: `PUT /api/pleroma/admin/reports/:id` is now `PATCH /api/pleroma/admin/reports`, see admin_api.md for details
|
- **Breaking:** Admin API: `PUT /api/pleroma/admin/reports/:id` is now `PATCH /api/pleroma/admin/reports`, see admin_api.md for details
|
||||||
- **Breaking:** `/api/pleroma/admin/users/invite_token` now uses `POST`, changed accepted params and returns full invite in json instead of only token string.
|
- **Breaking:** `/api/pleroma/admin/users/invite_token` now uses `POST`, changed accepted params and returns full invite in json instead of only token string.
|
||||||
- **Breaking** replying to reports is now "report notes", enpoint changed from `POST /api/pleroma/admin/reports/:id/respond` to `POST /api/pleroma/admin/reports/:id/notes`
|
- **Breaking** replying to reports is now "report notes", enpoint changed from `POST /api/pleroma/admin/reports/:id/respond` to `POST /api/pleroma/admin/reports/:id/notes`
|
||||||
|
- Mastodon API: stopped sanitizing display names, field names and subject fields since they are supposed to be treated as plaintext
|
||||||
- Admin API: Return `total` when querying for reports
|
- Admin API: Return `total` when querying for reports
|
||||||
- Mastodon API: Return `pleroma.direct_conversation_id` when creating a direct message (`POST /api/v1/statuses`)
|
- Mastodon API: Return `pleroma.direct_conversation_id` when creating a direct message (`POST /api/v1/statuses`)
|
||||||
- Admin API: Return link alongside with token on password reset
|
- Admin API: Return link alongside with token on password reset
|
||||||
|
@ -53,6 +59,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- `:chat_limit` option to limit chat characters.
|
- `:chat_limit` option to limit chat characters.
|
||||||
|
- `cleanup_attachments` option to remove attachments along with statuses. Does not affect duplicate files and attachments without status. Enabling this will increase load to database when deleting statuses on larger instances.
|
||||||
- Refreshing poll results for remote polls
|
- Refreshing poll results for remote polls
|
||||||
- Authentication: Added rate limit for password-authorized actions / login existence checks
|
- Authentication: Added rate limit for password-authorized actions / login existence checks
|
||||||
- Static Frontend: Add the ability to render user profiles and notices server-side without requiring JS app.
|
- Static Frontend: Add the ability to render user profiles and notices server-side without requiring JS app.
|
||||||
|
@ -105,6 +112,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Mastodon API: Change emoji reaction reply format once more
|
- Mastodon API: Change emoji reaction reply format once more
|
||||||
- Configuration: `feed.logo` option for tag feed.
|
- Configuration: `feed.logo` option for tag feed.
|
||||||
- Tag feed: `/tags/:tag.rss` - list public statuses by hashtag.
|
- Tag feed: `/tags/:tag.rss` - list public statuses by hashtag.
|
||||||
|
- Mastodon API: Add `reacted` property to `emoji_reactions`
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
@ -113,7 +121,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- MRF: `Delete` activities being exempt from MRF policies
|
- MRF: `Delete` activities being exempt from MRF policies
|
||||||
- OTP releases: Not being able to configure OAuth expired token cleanup interval
|
- OTP releases: Not being able to configure OAuth expired token cleanup interval
|
||||||
- OTP releases: Not being able to configure HTML sanitization policy
|
- OTP releases: Not being able to configure HTML sanitization policy
|
||||||
|
- OTP releases: Not being able to change upload limit (again)
|
||||||
- Favorites timeline now ordered by favorite date instead of post date
|
- Favorites timeline now ordered by favorite date instead of post date
|
||||||
|
- Support for cancellation of a follow request
|
||||||
<details>
|
<details>
|
||||||
<summary>API Changes</summary>
|
<summary>API Changes</summary>
|
||||||
|
|
||||||
|
|
|
@ -271,7 +271,8 @@
|
||||||
account_field_name_length: 512,
|
account_field_name_length: 512,
|
||||||
account_field_value_length: 2048,
|
account_field_value_length: 2048,
|
||||||
external_user_synchronization: true,
|
external_user_synchronization: true,
|
||||||
extended_nickname_format: true
|
extended_nickname_format: true,
|
||||||
|
cleanup_attachments: false
|
||||||
|
|
||||||
config :pleroma, :feed,
|
config :pleroma, :feed,
|
||||||
post_title: %{
|
post_title: %{
|
||||||
|
@ -508,7 +509,6 @@
|
||||||
|
|
||||||
config :auto_linker,
|
config :auto_linker,
|
||||||
opts: [
|
opts: [
|
||||||
scheme: true,
|
|
||||||
extra: true,
|
extra: true,
|
||||||
# TODO: Set to :no_scheme when it works properly
|
# TODO: Set to :no_scheme when it works properly
|
||||||
validate_tld: true,
|
validate_tld: true,
|
||||||
|
@ -596,11 +596,21 @@
|
||||||
config :http_signatures,
|
config :http_signatures,
|
||||||
adapter: Pleroma.Signature
|
adapter: Pleroma.Signature
|
||||||
|
|
||||||
config :pleroma, :rate_limit, authentication: {60_000, 15}
|
config :pleroma, :rate_limit,
|
||||||
|
authentication: {60_000, 15},
|
||||||
|
search: [{1000, 10}, {1000, 30}],
|
||||||
|
app_account_creation: {1_800_000, 25},
|
||||||
|
relations_actions: {10_000, 10},
|
||||||
|
relation_id_action: {60_000, 2},
|
||||||
|
statuses_actions: {10_000, 15},
|
||||||
|
status_id_action: {60_000, 3},
|
||||||
|
password_reset: {1_800_000, 5},
|
||||||
|
account_confirmation_resend: {8_640_000, 5},
|
||||||
|
ap_routes: {60_000, 15}
|
||||||
|
|
||||||
config :pleroma, Pleroma.ActivityExpiration, enabled: true
|
config :pleroma, Pleroma.ActivityExpiration, enabled: true
|
||||||
|
|
||||||
config :pleroma, Pleroma.Plugs.RemoteIp, enabled: false
|
config :pleroma, Pleroma.Plugs.RemoteIp, enabled: true
|
||||||
|
|
||||||
config :pleroma, :static_fe, enabled: false
|
config :pleroma, :static_fe, enabled: false
|
||||||
|
|
||||||
|
|
|
@ -764,6 +764,15 @@
|
||||||
"Set to `true` to use extended local nicknames format (allows underscores/dashes)." <>
|
"Set to `true` to use extended local nicknames format (allows underscores/dashes)." <>
|
||||||
" This will break federation with older software for theses nicknames."
|
" This will break federation with older software for theses nicknames."
|
||||||
},
|
},
|
||||||
|
%{
|
||||||
|
key: :cleanup_attachments,
|
||||||
|
type: :boolean,
|
||||||
|
description: """
|
||||||
|
"Set to `true` 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.
|
||||||
|
"""
|
||||||
|
},
|
||||||
%{
|
%{
|
||||||
key: :max_pinned_statuses,
|
key: :max_pinned_statuses,
|
||||||
type: :integer,
|
type: :integer,
|
||||||
|
@ -862,7 +871,7 @@
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :limit_to_local_content,
|
key: :limit_to_local_content,
|
||||||
type: [:atom, false],
|
type: {:dropdown, :atom},
|
||||||
description:
|
description:
|
||||||
"Limit unauthenticated users to search for local statutes and users only. Default: `:unauthenticated`.",
|
"Limit unauthenticated users to search for local statutes and users only. Default: `:unauthenticated`.",
|
||||||
suggestions: [
|
suggestions: [
|
||||||
|
@ -933,7 +942,7 @@
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
key: :level,
|
key: :level,
|
||||||
type: :atom,
|
type: {:dropdown, :atom},
|
||||||
description: "Log level",
|
description: "Log level",
|
||||||
suggestions: [:debug, :info, :warn, :error]
|
suggestions: [:debug, :info, :warn, :error]
|
||||||
},
|
},
|
||||||
|
@ -965,7 +974,7 @@
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
key: :level,
|
key: :level,
|
||||||
type: :atom,
|
type: {:dropdown, :atom},
|
||||||
description: "Log level",
|
description: "Log level",
|
||||||
suggestions: [:debug, :info, :warn, :error]
|
suggestions: [:debug, :info, :warn, :error]
|
||||||
},
|
},
|
||||||
|
@ -989,7 +998,7 @@
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
key: :level,
|
key: :level,
|
||||||
type: :atom,
|
type: {:dropdown, :atom},
|
||||||
description: "Log level",
|
description: "Log level",
|
||||||
suggestions: [:debug, :info, :warn, :error]
|
suggestions: [:debug, :info, :warn, :error]
|
||||||
},
|
},
|
||||||
|
@ -1960,7 +1969,7 @@
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :verbose,
|
key: :verbose,
|
||||||
type: [:atom, false],
|
type: {:dropdown, :atom},
|
||||||
description: "Logs verbose mode",
|
description: "Logs verbose mode",
|
||||||
suggestions: [false, :error, :warn, :info, :debug]
|
suggestions: [false, :error, :warn, :info, :debug]
|
||||||
},
|
},
|
||||||
|
@ -2169,12 +2178,7 @@
|
||||||
%{
|
%{
|
||||||
key: :new_window,
|
key: :new_window,
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Set to `false` to remove target='_blank' attribute"
|
description: "Link urls will open in new window/tab"
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :scheme,
|
|
||||||
type: :boolean,
|
|
||||||
description: "Set to `true` to link urls with schema http://google.com"
|
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :truncate,
|
key: :truncate,
|
||||||
|
|
|
@ -665,11 +665,9 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
||||||
- 404 Not Found `"Not found"`
|
- 404 Not Found `"Not found"`
|
||||||
- On success: 200 OK `{}`
|
- On success: 200 OK `{}`
|
||||||
|
|
||||||
## `GET /api/pleroma/admin/config/migrate_from_db`
|
## `GET /api/pleroma/admin/restart`
|
||||||
|
|
||||||
### Run mix task pleroma.config migrate_from_db
|
### Restarts pleroma application
|
||||||
|
|
||||||
Copies all settings from database to `config/{env}.exported_from_db.secret.exs` with deletion from the table. Where `{env}` is the environment in which `pleroma` is running.
|
|
||||||
|
|
||||||
- Params: none
|
- Params: none
|
||||||
- Response:
|
- Response:
|
||||||
|
@ -691,7 +689,6 @@ Copies all settings from database to `config/{env}.exported_from_db.secret.exs`
|
||||||
- Response:
|
- Response:
|
||||||
- On failure:
|
- On failure:
|
||||||
- 400 Bad Request `"To use this endpoint you need to enable configuration from database."`
|
- 400 Bad Request `"To use this endpoint you need to enable configuration from database."`
|
||||||
- 400 Bad Request `"To use configuration from database migrate your settings to database."`
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|
|
@ -29,7 +29,7 @@ Has these additional fields under the `pleroma` object:
|
||||||
- `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`
|
- `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`
|
||||||
- `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire
|
- `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire
|
||||||
- `thread_muted`: true if the thread the post belongs to is muted
|
- `thread_muted`: true if the thread the post belongs to is muted
|
||||||
- `emoji_reactions`: A list with emoji / reaction maps. The format is {emoji: "☕", count: 1}. Contains no information about the reacting users, for that use the `emoji_reactions_by` endpoint.
|
- `emoji_reactions`: A list with emoji / reaction maps. The format is `{emoji: "☕", count: 1, reacted: true}`. Contains no information about the reacting users, for that use the `emoji_reactions_by` endpoint.
|
||||||
|
|
||||||
## Attachments
|
## Attachments
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ The `type` value is `move`. Has an additional field:
|
||||||
|
|
||||||
- `target`: new account
|
- `target`: new account
|
||||||
|
|
||||||
### EmojiReaction Notification
|
### EmojiReact Notification
|
||||||
|
|
||||||
The `type` value is `pleroma:emoji_reaction`. Has these fields:
|
The `type` value is `pleroma:emoji_reaction`. Has these fields:
|
||||||
|
|
||||||
|
|
|
@ -455,7 +455,7 @@ Emoji reactions work a lot like favourites do. They make it possible to react to
|
||||||
* Example Response:
|
* Example Response:
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{"emoji": "😀", "count": 2, "accounts": [{"id" => "xyz.."...}, {"id" => "zyx..."}]},
|
{"emoji": "😀", "count": 2, "reacted": true, "accounts": [{"id" => "xyz.."...}, {"id" => "zyx..."}]},
|
||||||
{"emoji": "☕", "count": 1, "accounts": [{"id" => "abc..."}]}
|
{"emoji": "☕", "count": 1, "reacted": false, "accounts": [{"id" => "abc..."}]}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
# Configuring instance
|
|
||||||
You can configure your instance from admin interface. You need account with admin rights and little change in config file, which will allow settings configuration from database.
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
config :pleroma, configurable_from_database: true
|
|
||||||
```
|
|
||||||
|
|
||||||
## How it works
|
|
||||||
Settings are stored in database and are applied in `runtime` after each change. Most of the settings take effect immediately, except some, which need instance reboot. These settings are needed in `compile time`, that's why settings are duplicated to the file.
|
|
||||||
|
|
||||||
File with duplicated settings is located in `config/{env}.exported_from_db.exs` if pleroma is runned from source. For prod env it will be `config/prod.exported_from_db.exs`.
|
|
||||||
|
|
||||||
For releases: `/etc/pleroma/prod.exported_from_db.secret.exs` or `PLEROMA_CONFIG_PATH/prod.exported_from_db.exs`.
|
|
||||||
|
|
||||||
## How to set it up
|
|
||||||
You need to migrate your existing settings to the database. This task will migrate only added by user settings.
|
|
||||||
For example you add settings to `prod.secret.exs` file, only these settings will be migrated to database. For release it will be `/etc/pleroma/config.exs` or `PLEROMA_CONFIG_PATH`.
|
|
||||||
You can do this with mix task (all config files will remain untouched):
|
|
||||||
|
|
||||||
```sh tab="OTP"
|
|
||||||
./bin/pleroma_ctl config migrate_to_db
|
|
||||||
```
|
|
||||||
|
|
||||||
```sh tab="From Source"
|
|
||||||
mix pleroma.config migrate_to_db
|
|
||||||
```
|
|
||||||
|
|
||||||
Now you can change settings in admin interface. After each save, settings from database are duplicated to the `config/{env}.exported_from_db.exs` file.
|
|
||||||
|
|
||||||
<span style="color:red">**ATTENTION**</span>
|
|
||||||
|
|
||||||
**<span style="color:red">Be careful while changing the settings. Every inaccurate configuration change can break the federation or the instance load.</span>**
|
|
||||||
|
|
||||||
*Compile time settings, which require instance reboot and can break instance loading:*
|
|
||||||
- all settings inside these keys:
|
|
||||||
- `:hackney_pools`
|
|
||||||
- `:chat`
|
|
||||||
- partially settings inside these keys:
|
|
||||||
- `:seconds_valid` in `Pleroma.Captcha`
|
|
||||||
- `:proxy_remote` in `Pleroma.Upload`
|
|
||||||
- `:upload_limit` in `:instance`
|
|
||||||
|
|
||||||
## How to dump settings from database to file
|
|
||||||
|
|
||||||
*Adding `-d` flag will delete migrated settings from database table.*
|
|
||||||
|
|
||||||
```sh tab="OTP"
|
|
||||||
./bin/pleroma_ctl config migrate_from_db [-d]
|
|
||||||
```
|
|
||||||
|
|
||||||
```sh tab="From Source"
|
|
||||||
mix pleroma.config migrate_from_db [-d]
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## How to completely remove it
|
|
||||||
|
|
||||||
1. Truncate or delete all values from `config` table
|
|
||||||
```sql
|
|
||||||
TRUNCATE TABLE config;
|
|
||||||
```
|
|
||||||
2. Delete `config/{env}.exported_from_db.exs`.
|
|
||||||
|
|
||||||
For `prod` env:
|
|
||||||
```bash
|
|
||||||
cd /opt/pleroma
|
|
||||||
cp config/prod.exported_from_db.exs config/exported_from_db.back
|
|
||||||
rm -rf config/prod.exported_from_db.exs
|
|
||||||
```
|
|
||||||
*If you don't want to backup settings, you can skip step with `cp` command.*
|
|
||||||
|
|
||||||
3. Set configurable_from_database to `false`.
|
|
||||||
```elixir
|
|
||||||
config :pleroma, configurable_from_database: false
|
|
||||||
```
|
|
||||||
4. Restart pleroma instance
|
|
||||||
```bash
|
|
||||||
sudo service pleroma restart
|
|
||||||
```
|
|
|
@ -1,12 +1,16 @@
|
||||||
# Transfering the config to/from the database
|
# Transfering the config to/from the database
|
||||||
|
|
||||||
!!! danger
|
|
||||||
This is a Work In Progress, not usable just yet.
|
|
||||||
|
|
||||||
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
||||||
|
|
||||||
## Transfer config from file to DB.
|
## Transfer config from file to DB.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
You need to add the following to your config before executing this command:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
config :pleroma, configurable_from_database: true
|
||||||
|
```
|
||||||
|
|
||||||
```sh tab="OTP"
|
```sh tab="OTP"
|
||||||
./bin/pleroma_ctl config migrate_to_db
|
./bin/pleroma_ctl config migrate_to_db
|
||||||
```
|
```
|
||||||
|
@ -18,7 +22,15 @@ mix pleroma.config migrate_to_db
|
||||||
|
|
||||||
## Transfer config from DB to `config/env.exported_from_db.secret.exs`
|
## Transfer config from DB to `config/env.exported_from_db.secret.exs`
|
||||||
|
|
||||||
To delete transfered settings from database optional flag `-d` can be used. <env> is `prod` by default.
|
!!! note
|
||||||
|
In-Database configuration will still be applied after executing this command unless you set the following in your config:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
config :pleroma, configurable_from_database: false
|
||||||
|
```
|
||||||
|
|
||||||
|
To delete transfered settings from database optional flag `-d` can be used. `<env>` is `prod` by default.
|
||||||
|
|
||||||
```sh tab="OTP"
|
```sh tab="OTP"
|
||||||
./bin/pleroma_ctl config migrate_from_db [--env=<env>] [-d]
|
./bin/pleroma_ctl config migrate_from_db [--env=<env>] [-d]
|
||||||
```
|
```
|
||||||
|
|
|
@ -69,6 +69,7 @@ You shouldn't edit the base config directly to avoid breakages and merge conflic
|
||||||
* `account_field_name_length`: An account field name maximum length (default: `512`).
|
* `account_field_name_length`: An account field name maximum length (default: `512`).
|
||||||
* `account_field_value_length`: An account field value maximum length (default: `2048`).
|
* `account_field_value_length`: An account field value maximum length (default: `2048`).
|
||||||
* `external_user_synchronization`: Enabling following/followers counters synchronization for external users.
|
* `external_user_synchronization`: Enabling following/followers counters synchronization for external users.
|
||||||
|
* `cleanup_attachments`: Remove attachments along with statuses. Does not affect duplicate files and attachments without status. Enabling this will increase load to database when deleting statuses on larger instances.
|
||||||
|
|
||||||
## Federation
|
## Federation
|
||||||
### MRF policies
|
### MRF policies
|
||||||
|
@ -308,16 +309,15 @@ This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls start
|
||||||
Available options:
|
Available options:
|
||||||
|
|
||||||
* `enabled` - Enable/disable the plug. Defaults to `false`.
|
* `enabled` - Enable/disable the plug. Defaults to `false`.
|
||||||
* `headers` - A list of strings naming the `req_headers` to use when deriving the `remote_ip`. Order does not matter. Defaults to `~w[forwarded x-forwarded-for x-client-ip x-real-ip]`.
|
* `headers` - A list of strings naming the `req_headers` to use when deriving the `remote_ip`. Order does not matter. Defaults to `["x-forwarded-for"]`.
|
||||||
* `proxies` - A list of strings in [CIDR](https://en.wikipedia.org/wiki/CIDR) notation specifying the IPs of known proxies. Defaults to `[]`.
|
* `proxies` - A list of strings in [CIDR](https://en.wikipedia.org/wiki/CIDR) notation specifying the IPs of known proxies. Defaults to `[]`.
|
||||||
* `reserved` - Defaults to [localhost](https://en.wikipedia.org/wiki/Localhost) and [private network](https://en.wikipedia.org/wiki/Private_network).
|
* `reserved` - Defaults to [localhost](https://en.wikipedia.org/wiki/Localhost) and [private network](https://en.wikipedia.org/wiki/Private_network).
|
||||||
|
|
||||||
|
|
||||||
### :rate_limit
|
### :rate_limit
|
||||||
|
|
||||||
This is an advanced feature and disabled by default.
|
!!! note
|
||||||
|
If your instance is behind a reverse proxy ensure [`Pleroma.Plugs.RemoteIp`](#pleroma-plugs-remoteip) is enabled (it is enabled by default).
|
||||||
If your instance is behind a reverse proxy you must enable and configure [`Pleroma.Plugs.RemoteIp`](#pleroma-plugs-remoteip).
|
|
||||||
|
|
||||||
A keyword list of rate limiters where a key is a limiter name and value is the limiter configuration. The basic configuration is a tuple where:
|
A keyword list of rate limiters where a key is a limiter name and value is the limiter configuration. The basic configuration is a tuple where:
|
||||||
|
|
||||||
|
@ -326,14 +326,31 @@ A keyword list of rate limiters where a key is a limiter name and value is the l
|
||||||
|
|
||||||
It is also possible to have different limits for unauthenticated and authenticated users: the keyword value must be a list of two tuples where the first one is a config for unauthenticated users and the second one is for authenticated.
|
It is also possible to have different limits for unauthenticated and authenticated users: the keyword value must be a list of two tuples where the first one is a config for unauthenticated users and the second one is for authenticated.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
config :pleroma, :rate_limit,
|
||||||
|
authentication: {60_000, 15},
|
||||||
|
search: [{1000, 10}, {1000, 30}]
|
||||||
|
```
|
||||||
|
|
||||||
|
Means that:
|
||||||
|
|
||||||
|
1. In 60 seconds, 15 authentication attempts can be performed from the same IP address.
|
||||||
|
2. In 1 second, 10 search requests can be performed from the same IP adress by unauthenticated users, while authenticated users can perform 30 search requests per second.
|
||||||
|
|
||||||
Supported rate limiters:
|
Supported rate limiters:
|
||||||
|
|
||||||
* `:search` for the search requests (account & status search etc.)
|
* `:search` - Account/Status search.
|
||||||
* `:app_account_creation` for registering user accounts from the same IP address
|
* `:app_account_creation` - Account registration from the API.
|
||||||
* `:relations_actions` for actions on relations with all users (follow, unfollow)
|
* `:relations_actions` - Following/Unfollowing in general.
|
||||||
* `:relation_id_action` for actions on relation with a specific user (follow, unfollow)
|
* `:relation_id_action` - Following/Unfollowing for a specific user.
|
||||||
* `:statuses_actions` for create / delete / fav / unfav / reblog / unreblog actions on any statuses
|
* `:statuses_actions` - Status actions such as: (un)repeating, (un)favouriting, creating, deleting.
|
||||||
* `:status_id_action` for fav / unfav or reblog / unreblog actions on the same status by the same user
|
* `:status_id_action` - (un)Repeating/(un)Favouriting a particular status.
|
||||||
|
* `:authentication` - Authentication actions, i.e getting an OAuth token.
|
||||||
|
* `:password_reset` - Requesting password reset emails.
|
||||||
|
* `:account_confirmation_resend` - Requesting resending account confirmation emails.
|
||||||
|
* `:ap_routes` - Requesting statuses via ActivityPub.
|
||||||
|
|
||||||
### :web_cache_ttl
|
### :web_cache_ttl
|
||||||
|
|
||||||
|
@ -839,4 +856,5 @@ config :auto_linker,
|
||||||
|
|
||||||
|
|
||||||
## :configurable_from_database
|
## :configurable_from_database
|
||||||
Enable/disable configuration from database.
|
|
||||||
|
Boolean, enables/disables in-database configuration. Read [Transfering the config to/from the database](../administration/CLI_tasks/config.md) for more information.
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
defmodule Mix.Tasks.Pleroma.Email do
|
defmodule Mix.Tasks.Pleroma.Email do
|
||||||
use Mix.Task
|
use Mix.Task
|
||||||
|
import Mix.Pleroma
|
||||||
|
|
||||||
@shortdoc "Simple Email test"
|
@shortdoc "Simple Email test"
|
||||||
@moduledoc File.read!("docs/administration/CLI_tasks/email.md")
|
@moduledoc File.read!("docs/administration/CLI_tasks/email.md")
|
||||||
|
@ -18,8 +19,6 @@ def run(["test" | args]) do
|
||||||
email = Pleroma.Emails.AdminEmail.test_email(options[:to])
|
email = Pleroma.Emails.AdminEmail.test_email(options[:to])
|
||||||
{:ok, _} = Pleroma.Emails.Mailer.deliver(email)
|
{:ok, _} = Pleroma.Emails.Mailer.deliver(email)
|
||||||
|
|
||||||
Mix.shell().info(
|
shell_info("Test email has been sent to #{inspect(email.to)} from #{inspect(email.from)}")
|
||||||
"Test email has been sent to #{inspect(email.to)} from #{inspect(email.from)}"
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -31,7 +31,7 @@ defmodule Pleroma.Activity do
|
||||||
"Announce" => "reblog",
|
"Announce" => "reblog",
|
||||||
"Like" => "favourite",
|
"Like" => "favourite",
|
||||||
"Move" => "move",
|
"Move" => "move",
|
||||||
"EmojiReaction" => "pleroma:emoji_reaction"
|
"EmojiReact" => "pleroma:emoji_reaction"
|
||||||
}
|
}
|
||||||
|
|
||||||
@mastodon_to_ap_notification_types for {k, v} <- @mastodon_notification_types,
|
@mastodon_to_ap_notification_types for {k, v} <- @mastodon_notification_types,
|
||||||
|
|
|
@ -33,6 +33,7 @@ def user_agent do
|
||||||
def start(_type, _args) do
|
def start(_type, _args) do
|
||||||
Pleroma.HTML.compile_scrubbers()
|
Pleroma.HTML.compile_scrubbers()
|
||||||
Pleroma.Config.DeprecationWarnings.warn()
|
Pleroma.Config.DeprecationWarnings.warn()
|
||||||
|
Pleroma.Plugs.HTTPSecurityPlug.warn_if_disabled()
|
||||||
Pleroma.Repo.check_migrations_applied!()
|
Pleroma.Repo.check_migrations_applied!()
|
||||||
setup_instrumenters()
|
setup_instrumenters()
|
||||||
load_custom_modules()
|
load_custom_modules()
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Config.Loader do
|
defmodule Pleroma.Config.Loader do
|
||||||
@paths ["config/config.exs", "config/#{Mix.env()}.exs"]
|
|
||||||
|
|
||||||
@reject_keys [
|
@reject_keys [
|
||||||
Pleroma.Repo,
|
Pleroma.Repo,
|
||||||
Pleroma.Web.Endpoint,
|
Pleroma.Web.Endpoint,
|
||||||
|
@ -35,8 +33,8 @@ defp do_merge(conf1, conf2), do: Mix.Config.merge(conf1, conf2)
|
||||||
def load_and_merge do
|
def load_and_merge do
|
||||||
all_paths =
|
all_paths =
|
||||||
if Pleroma.Config.get(:release),
|
if Pleroma.Config.get(:release),
|
||||||
do: @paths ++ ["config/releases.exs"],
|
do: ["config/config.exs", "config/releases.exs"],
|
||||||
else: @paths
|
else: ["config/config.exs"]
|
||||||
|
|
||||||
all_paths
|
all_paths
|
||||||
|> Enum.map(&load(&1))
|
|> Enum.map(&load(&1))
|
||||||
|
|
|
@ -10,6 +10,30 @@ defmodule Pleroma.Config.TransferTask do
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
@type env() :: :test | :benchmark | :dev | :prod
|
||||||
|
|
||||||
|
@reboot_time_keys [
|
||||||
|
{:pleroma, :hackney_pools},
|
||||||
|
{:pleroma, :chat},
|
||||||
|
{:pleroma, Oban},
|
||||||
|
{:pleroma, :rate_limit},
|
||||||
|
{:pleroma, :markup},
|
||||||
|
{:plerome, :streamer}
|
||||||
|
]
|
||||||
|
|
||||||
|
@reboot_time_subkeys [
|
||||||
|
{:pleroma, Pleroma.Captcha, [:seconds_valid]},
|
||||||
|
{:pleroma, Pleroma.Upload, [:proxy_remote]},
|
||||||
|
{:pleroma, :instance, [:upload_limit]},
|
||||||
|
{:pleroma, :email_notifications, [:digest]},
|
||||||
|
{:pleroma, :oauth2, [:clean_expired_tokens]},
|
||||||
|
{:pleroma, Pleroma.ActivityExpiration, [:enabled]},
|
||||||
|
{:pleroma, Pleroma.ScheduledActivity, [:enabled]},
|
||||||
|
{:pleroma, :gopher, [:enabled]}
|
||||||
|
]
|
||||||
|
|
||||||
|
@reject [nil, :prometheus]
|
||||||
|
|
||||||
def start_link(_) do
|
def start_link(_) do
|
||||||
load_and_update_env()
|
load_and_update_env()
|
||||||
if Pleroma.Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Repo)
|
if Pleroma.Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Repo)
|
||||||
|
@ -17,21 +41,34 @@ def start_link(_) do
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec load_and_update_env([ConfigDB.t()]) :: :ok | false
|
@spec load_and_update_env([ConfigDB.t()]) :: :ok | false
|
||||||
def load_and_update_env(deleted \\ []) do
|
def load_and_update_env(deleted \\ [], restart_pleroma? \\ true) do
|
||||||
with true <- Pleroma.Config.get(:configurable_from_database),
|
with true <- Pleroma.Config.get(:configurable_from_database),
|
||||||
true <- Ecto.Adapters.SQL.table_exists?(Repo, "config"),
|
true <- Ecto.Adapters.SQL.table_exists?(Repo, "config"),
|
||||||
started_applications <- Application.started_applications() do
|
started_applications <- Application.started_applications() do
|
||||||
# We need to restart applications for loaded settings take effect
|
# We need to restart applications for loaded settings take effect
|
||||||
|
|
||||||
in_db = Repo.all(ConfigDB)
|
in_db = Repo.all(ConfigDB)
|
||||||
|
|
||||||
with_deleted = in_db ++ deleted
|
with_deleted = in_db ++ deleted
|
||||||
|
|
||||||
with_deleted
|
reject_for_restart = if restart_pleroma?, do: @reject, else: [:pleroma | @reject]
|
||||||
|> Enum.map(&merge_and_update(&1))
|
|
||||||
|> Enum.uniq()
|
applications =
|
||||||
# TODO: some problem with prometheus after restart!
|
with_deleted
|
||||||
|> Enum.reject(&(&1 in [:pleroma, nil, :prometheus]))
|
|> Enum.map(&merge_and_update(&1))
|
||||||
|> Enum.each(&restart(started_applications, &1))
|
|> Enum.uniq()
|
||||||
|
# TODO: some problem with prometheus after restart!
|
||||||
|
|> Enum.reject(&(&1 in reject_for_restart))
|
||||||
|
|
||||||
|
# to be ensured that pleroma will be restarted last
|
||||||
|
applications =
|
||||||
|
if :pleroma in applications do
|
||||||
|
List.delete(applications, :pleroma) ++ [:pleroma]
|
||||||
|
else
|
||||||
|
applications
|
||||||
|
end
|
||||||
|
|
||||||
|
Enum.each(applications, &restart(started_applications, &1, Pleroma.Config.get(:env)))
|
||||||
|
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
@ -43,12 +80,25 @@ defp merge_and_update(setting) do
|
||||||
group = ConfigDB.from_string(setting.group)
|
group = ConfigDB.from_string(setting.group)
|
||||||
|
|
||||||
default = Pleroma.Config.Holder.config(group, key)
|
default = Pleroma.Config.Holder.config(group, key)
|
||||||
merged_value = merge_value(setting, default, group, key)
|
value = ConfigDB.from_binary(setting.value)
|
||||||
|
|
||||||
|
merged_value =
|
||||||
|
if Ecto.get_meta(setting, :state) == :deleted do
|
||||||
|
default
|
||||||
|
else
|
||||||
|
if can_be_merged?(default, value) do
|
||||||
|
ConfigDB.merge_group(group, key, default, value)
|
||||||
|
else
|
||||||
|
value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
:ok = update_env(group, key, merged_value)
|
:ok = update_env(group, key, merged_value)
|
||||||
|
|
||||||
if group != :logger do
|
if group != :logger do
|
||||||
group
|
if group != :pleroma or pleroma_need_restart?(group, key, value) do
|
||||||
|
group
|
||||||
|
end
|
||||||
else
|
else
|
||||||
# change logger configuration in runtime, without restart
|
# change logger configuration in runtime, without restart
|
||||||
if Keyword.keyword?(merged_value) and
|
if Keyword.keyword?(merged_value) and
|
||||||
|
@ -76,22 +126,31 @@ defp merge_and_update(setting) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp merge_value(%{__meta__: %{state: :deleted}}, default, _group, _key), do: default
|
@spec pleroma_need_restart?(atom(), atom(), any()) :: boolean()
|
||||||
|
def pleroma_need_restart?(group, key, value) do
|
||||||
|
group_and_key_need_reboot?(group, key) or group_and_subkey_need_reboot?(group, key, value)
|
||||||
|
end
|
||||||
|
|
||||||
defp merge_value(setting, default, group, key) do
|
defp group_and_key_need_reboot?(group, key) do
|
||||||
value = ConfigDB.from_binary(setting.value)
|
Enum.any?(@reboot_time_keys, fn {g, k} -> g == group and k == key end)
|
||||||
|
end
|
||||||
|
|
||||||
if can_be_merged?(default, value) do
|
defp group_and_subkey_need_reboot?(group, key, value) do
|
||||||
ConfigDB.merge_group(group, key, default, value)
|
Keyword.keyword?(value) and
|
||||||
else
|
Enum.any?(@reboot_time_subkeys, fn {g, k, subkeys} ->
|
||||||
value
|
g == group and k == key and
|
||||||
end
|
Enum.any?(Keyword.keys(value), &(&1 in subkeys))
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp update_env(group, key, nil), do: Application.delete_env(group, key)
|
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 update_env(group, key, value), do: Application.put_env(group, key, value)
|
||||||
|
|
||||||
defp restart(started_applications, app) do
|
defp restart(_, :pleroma, :test), do: Logger.warn("pleroma restarted")
|
||||||
|
|
||||||
|
defp restart(_, :pleroma, _), do: send(Restarter.Pleroma, :after_boot)
|
||||||
|
|
||||||
|
defp restart(started_applications, app, _) do
|
||||||
with {^app, _, _} <- List.keyfind(started_applications, app, 0),
|
with {^app, _, _} <- List.keyfind(started_applications, app, 0),
|
||||||
:ok <- Application.stop(app) do
|
:ok <- Application.stop(app) do
|
||||||
:ok = Application.start(app)
|
:ok = Application.start(app)
|
||||||
|
|
|
@ -58,8 +58,8 @@ def follow(%User{} = follower, %User{} = following, state \\ "accept") do
|
||||||
|
|
||||||
def unfollow(%User{} = follower, %User{} = following) do
|
def unfollow(%User{} = follower, %User{} = following) do
|
||||||
case get(follower, following) do
|
case get(follower, following) do
|
||||||
nil -> {:ok, nil}
|
|
||||||
%__MODULE__{} = following_relationship -> Repo.delete(following_relationship)
|
%__MODULE__{} = following_relationship -> Repo.delete(following_relationship)
|
||||||
|
_ -> {:ok, nil}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,8 @@ defmodule Pleroma.Formatter do
|
||||||
@auto_linker_config hashtag: true,
|
@auto_linker_config hashtag: true,
|
||||||
hashtag_handler: &Pleroma.Formatter.hashtag_handler/4,
|
hashtag_handler: &Pleroma.Formatter.hashtag_handler/4,
|
||||||
mention: true,
|
mention: true,
|
||||||
mention_handler: &Pleroma.Formatter.mention_handler/4
|
mention_handler: &Pleroma.Formatter.mention_handler/4,
|
||||||
|
scheme: true
|
||||||
|
|
||||||
def escape_mention_handler("@" <> nickname = mention, buffer, _, _) do
|
def escape_mention_handler("@" <> nickname = mention, buffer, _, _) do
|
||||||
case User.get_cached_by_nickname(nickname) do
|
case User.get_cached_by_nickname(nickname) do
|
||||||
|
|
|
@ -323,7 +323,7 @@ def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = act
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_notifications(%Activity{data: %{"type" => type}} = activity)
|
def create_notifications(%Activity{data: %{"type" => type}} = activity)
|
||||||
when type in ["Like", "Announce", "Follow", "Move", "EmojiReaction"] do
|
when type in ["Like", "Announce", "Follow", "Move", "EmojiReact"] do
|
||||||
notifications =
|
notifications =
|
||||||
activity
|
activity
|
||||||
|> get_notified_from_activity()
|
|> get_notified_from_activity()
|
||||||
|
@ -354,7 +354,7 @@ def create_notification(%Activity{} = activity, %User{} = user) do
|
||||||
def get_notified_from_activity(activity, local_only \\ true)
|
def get_notified_from_activity(activity, local_only \\ true)
|
||||||
|
|
||||||
def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only)
|
def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only)
|
||||||
when type in ["Create", "Like", "Announce", "Follow", "Move", "EmojiReaction"] do
|
when type in ["Create", "Like", "Announce", "Follow", "Move", "EmojiReact"] do
|
||||||
[]
|
[]
|
||||||
|> Utils.maybe_notify_to_recipients(activity)
|
|> Utils.maybe_notify_to_recipients(activity)
|
||||||
|> Utils.maybe_notify_mentioned_recipients(activity)
|
|> Utils.maybe_notify_mentioned_recipients(activity)
|
||||||
|
|
|
@ -184,11 +184,14 @@ def delete(%Object{data: %{"id" => id}} = object) do
|
||||||
with {:ok, _obj} = swap_object_with_tombstone(object),
|
with {:ok, _obj} = swap_object_with_tombstone(object),
|
||||||
deleted_activity = Activity.delete_all_by_object_ap_id(id),
|
deleted_activity = Activity.delete_all_by_object_ap_id(id),
|
||||||
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}"),
|
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}"),
|
||||||
{:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path),
|
{:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do
|
||||||
{:ok, _} <-
|
with true <- Pleroma.Config.get([:instance, :cleanup_attachments]) do
|
||||||
Pleroma.Workers.AttachmentsCleanupWorker.enqueue("cleanup_attachments", %{
|
{:ok, _} =
|
||||||
"object" => object
|
Pleroma.Workers.AttachmentsCleanupWorker.enqueue("cleanup_attachments", %{
|
||||||
}) do
|
"object" => object
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
{:ok, object, deleted_activity}
|
{:ok, object, deleted_activity}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,8 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
def init(opts), do: opts
|
def init(opts), do: opts
|
||||||
|
|
||||||
def call(conn, _options) do
|
def call(conn, _options) do
|
||||||
|
@ -90,6 +92,51 @@ defp csp_string do
|
||||||
|> Enum.join("; ")
|
|> Enum.join("; ")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def warn_if_disabled do
|
||||||
|
unless Config.get([:http_security, :enabled]) do
|
||||||
|
Logger.warn("
|
||||||
|
.i;;;;i.
|
||||||
|
iYcviii;vXY:
|
||||||
|
.YXi .i1c.
|
||||||
|
.YC. . in7.
|
||||||
|
.vc. ...... ;1c.
|
||||||
|
i7, .. .;1;
|
||||||
|
i7, .. ... .Y1i
|
||||||
|
,7v .6MMM@; .YX,
|
||||||
|
.7;. ..IMMMMMM1 :t7.
|
||||||
|
.;Y. ;$MMMMMM9. :tc.
|
||||||
|
vY. .. .nMMM@MMU. ;1v.
|
||||||
|
i7i ... .#MM@M@C. .....:71i
|
||||||
|
it: .... $MMM@9;.,i;;;i,;tti
|
||||||
|
:t7. ..... 0MMMWv.,iii:::,,;St.
|
||||||
|
.nC. ..... IMMMQ..,::::::,.,czX.
|
||||||
|
.ct: ....... .ZMMMI..,:::::::,,:76Y.
|
||||||
|
c2: ......,i..Y$M@t..:::::::,,..inZY
|
||||||
|
vov ......:ii..c$MBc..,,,,,,,,,,..iI9i
|
||||||
|
i9Y ......iii:..7@MA,..,,,,,,,,,....;AA:
|
||||||
|
iIS. ......:ii::..;@MI....,............;Ez.
|
||||||
|
.I9. ......:i::::...8M1..................C0z.
|
||||||
|
.z9; ......:i::::,.. .i:...................zWX.
|
||||||
|
vbv ......,i::::,,. ................. :AQY
|
||||||
|
c6Y. .,...,::::,,..:t0@@QY. ................ :8bi
|
||||||
|
:6S. ..,,...,:::,,,..EMMMMMMI. ............... .;bZ,
|
||||||
|
:6o, .,,,,..:::,,,..i#MMMMMM#v................. YW2.
|
||||||
|
.n8i ..,,,,,,,::,,,,.. tMMMMM@C:.................. .1Wn
|
||||||
|
7Uc. .:::,,,,,::,,,,.. i1t;,..................... .UEi
|
||||||
|
7C...::::::::::::,,,,.. .................... vSi.
|
||||||
|
;1;...,,::::::,......... .................. Yz:
|
||||||
|
v97,......... .voC.
|
||||||
|
izAotX7777777777777777777777777777777777777777Y7n92:
|
||||||
|
.;CoIIIIIUAA666666699999ZZZZZZZZZZZZZZZZZZZZ6ov.
|
||||||
|
|
||||||
|
HTTP Security is disabled. Please re-enable it to prevent users from attacking
|
||||||
|
your instance and your users via malicious posts:
|
||||||
|
|
||||||
|
config :pleroma, :http_security, enabled: true
|
||||||
|
")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp maybe_send_sts_header(conn, true) do
|
defp maybe_send_sts_header(conn, true) do
|
||||||
max_age_sts = Config.get([:http_security, :sts_max_age])
|
max_age_sts = Config.get([:http_security, :sts_max_age])
|
||||||
max_age_ct = Config.get([:http_security, :ct_max_age])
|
max_age_ct = Config.get([:http_security, :ct_max_age])
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Plugs.Parsers do
|
|
||||||
@moduledoc "Initializes Plug.Parsers with upload limit set at boot time"
|
|
||||||
|
|
||||||
@behaviour Plug
|
|
||||||
|
|
||||||
def init(_opts) do
|
|
||||||
Plug.Parsers.init(
|
|
||||||
parsers: [:urlencoded, :multipart, :json],
|
|
||||||
pass: ["*/*"],
|
|
||||||
json_decoder: Jason,
|
|
||||||
length: Pleroma.Config.get([:instance, :upload_limit]),
|
|
||||||
body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
defdelegate call(conn, opts), to: Plug.Parsers
|
|
||||||
end
|
|
|
@ -67,6 +67,8 @@ defmodule Pleroma.Plugs.RateLimiter do
|
||||||
alias Pleroma.Plugs.RateLimiter.LimiterSupervisor
|
alias Pleroma.Plugs.RateLimiter.LimiterSupervisor
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
def init(opts) do
|
def init(opts) do
|
||||||
limiter_name = Keyword.get(opts, :name)
|
limiter_name = Keyword.get(opts, :name)
|
||||||
|
|
||||||
|
@ -89,18 +91,39 @@ def init(opts) do
|
||||||
def call(conn, nil), do: conn
|
def call(conn, nil), do: conn
|
||||||
|
|
||||||
def call(conn, settings) do
|
def call(conn, settings) do
|
||||||
settings
|
case disabled?() do
|
||||||
|> incorporate_conn_info(conn)
|
true ->
|
||||||
|> check_rate()
|
if Pleroma.Config.get(:env) == :prod,
|
||||||
|> case do
|
do: Logger.warn("Rate limiter is disabled for localhost/socket")
|
||||||
{:ok, _count} ->
|
|
||||||
conn
|
conn
|
||||||
|
|
||||||
{:error, _count} ->
|
false ->
|
||||||
render_throttled_error(conn)
|
settings
|
||||||
|
|> incorporate_conn_info(conn)
|
||||||
|
|> check_rate()
|
||||||
|
|> case do
|
||||||
|
{:ok, _count} ->
|
||||||
|
conn
|
||||||
|
|
||||||
|
{:error, _count} ->
|
||||||
|
render_throttled_error(conn)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def disabled? do
|
||||||
|
localhost_or_socket =
|
||||||
|
Pleroma.Config.get([Pleroma.Web.Endpoint, :http, :ip])
|
||||||
|
|> Tuple.to_list()
|
||||||
|
|> Enum.join(".")
|
||||||
|
|> String.match?(~r/^local|^127.0.0.1/)
|
||||||
|
|
||||||
|
remote_ip_disabled = not Pleroma.Config.get([Pleroma.Plugs.RemoteIp, :enabled])
|
||||||
|
|
||||||
|
localhost_or_socket and remote_ip_disabled
|
||||||
|
end
|
||||||
|
|
||||||
def inspect_bucket(conn, name_root, settings) do
|
def inspect_bucket(conn, name_root, settings) do
|
||||||
settings =
|
settings =
|
||||||
settings
|
settings
|
||||||
|
|
|
@ -10,10 +10,7 @@ defmodule Pleroma.Plugs.RemoteIp do
|
||||||
@behaviour Plug
|
@behaviour Plug
|
||||||
|
|
||||||
@headers ~w[
|
@headers ~w[
|
||||||
forwarded
|
|
||||||
x-forwarded-for
|
x-forwarded-for
|
||||||
x-client-ip
|
|
||||||
x-real-ip
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# https://en.wikipedia.org/wiki/Localhost
|
# https://en.wikipedia.org/wiki/Localhost
|
||||||
|
|
|
@ -647,25 +647,48 @@ def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
|
||||||
|
{:error, "Not subscribed!"}
|
||||||
|
end
|
||||||
|
|
||||||
def unfollow(%User{} = follower, %User{} = followed) do
|
def unfollow(%User{} = follower, %User{} = followed) do
|
||||||
if following?(follower, followed) and follower.ap_id != followed.ap_id do
|
case get_follow_state(follower, followed) do
|
||||||
FollowingRelationship.unfollow(follower, followed)
|
state when state in ["accept", "pending"] ->
|
||||||
|
FollowingRelationship.unfollow(follower, followed)
|
||||||
|
{:ok, followed} = update_follower_count(followed)
|
||||||
|
|
||||||
{:ok, followed} = update_follower_count(followed)
|
{:ok, follower} =
|
||||||
|
follower
|
||||||
|
|> update_following_count()
|
||||||
|
|> set_cache()
|
||||||
|
|
||||||
{:ok, follower} =
|
{:ok, follower, Utils.fetch_latest_follow(follower, followed)}
|
||||||
follower
|
|
||||||
|> update_following_count()
|
|
||||||
|> set_cache()
|
|
||||||
|
|
||||||
{:ok, follower, Utils.fetch_latest_follow(follower, followed)}
|
nil ->
|
||||||
else
|
{:error, "Not subscribed!"}
|
||||||
{:error, "Not subscribed!"}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defdelegate following?(follower, followed), to: FollowingRelationship
|
defdelegate following?(follower, followed), to: FollowingRelationship
|
||||||
|
|
||||||
|
def get_follow_state(%User{} = follower, %User{} = following) do
|
||||||
|
following_relationship = FollowingRelationship.get(follower, following)
|
||||||
|
|
||||||
|
case {following_relationship, following.local} do
|
||||||
|
{nil, false} ->
|
||||||
|
case Utils.fetch_latest_follow(follower, following) do
|
||||||
|
%{data: %{"state" => state}} when state in ["pending", "accept"] -> state
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
|
||||||
|
{%{state: state}, _} ->
|
||||||
|
state
|
||||||
|
|
||||||
|
{nil, _} ->
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def locked?(%User{} = user) do
|
def locked?(%User{} = user) do
|
||||||
user.locked || false
|
user.locked || false
|
||||||
end
|
end
|
||||||
|
|
|
@ -325,12 +325,14 @@ def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
|
||||||
def react_with_emoji(user, object, emoji, options \\ []) do
|
def react_with_emoji(user, object, emoji, options \\ []) do
|
||||||
with local <- Keyword.get(options, :local, true),
|
with local <- Keyword.get(options, :local, true),
|
||||||
activity_id <- Keyword.get(options, :activity_id, nil),
|
activity_id <- Keyword.get(options, :activity_id, nil),
|
||||||
Pleroma.Emoji.is_unicode_emoji?(emoji),
|
true <- Pleroma.Emoji.is_unicode_emoji?(emoji),
|
||||||
reaction_data <- make_emoji_reaction_data(user, object, emoji, activity_id),
|
reaction_data <- make_emoji_reaction_data(user, object, emoji, activity_id),
|
||||||
{:ok, activity} <- insert(reaction_data, local),
|
{:ok, activity} <- insert(reaction_data, local),
|
||||||
{:ok, object} <- add_emoji_reaction_to_object(activity, object),
|
{:ok, object} <- add_emoji_reaction_to_object(activity, object),
|
||||||
:ok <- maybe_federate(activity) do
|
:ok <- maybe_federate(activity) do
|
||||||
{:ok, activity, object}
|
{:ok, activity, object}
|
||||||
|
else
|
||||||
|
e -> {:error, e}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -345,6 +347,8 @@ def unreact_with_emoji(user, reaction_id, options \\ []) do
|
||||||
{:ok, object} <- remove_emoji_reaction_from_object(reaction_activity, object),
|
{:ok, object} <- remove_emoji_reaction_from_object(reaction_activity, object),
|
||||||
:ok <- maybe_federate(activity) do
|
:ok <- maybe_federate(activity) do
|
||||||
{:ok, activity, object}
|
{:ok, activity, object}
|
||||||
|
else
|
||||||
|
e -> {:error, e}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -580,7 +580,7 @@ def handle_incoming(
|
||||||
"star" => "⭐"
|
"star" => "⭐"
|
||||||
}
|
}
|
||||||
|
|
||||||
@doc "Rewrite misskey likes into EmojiReactions"
|
@doc "Rewrite misskey likes into EmojiReacts"
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{
|
%{
|
||||||
"type" => "Like",
|
"type" => "Like",
|
||||||
|
@ -589,7 +589,7 @@ def handle_incoming(
|
||||||
options
|
options
|
||||||
) do
|
) do
|
||||||
data
|
data
|
||||||
|> Map.put("type", "EmojiReaction")
|
|> Map.put("type", "EmojiReact")
|
||||||
|> Map.put("content", @misskey_reactions[reaction] || reaction)
|
|> Map.put("content", @misskey_reactions[reaction] || reaction)
|
||||||
|> handle_incoming(options)
|
|> handle_incoming(options)
|
||||||
end
|
end
|
||||||
|
@ -610,7 +610,7 @@ def handle_incoming(
|
||||||
|
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{
|
%{
|
||||||
"type" => "EmojiReaction",
|
"type" => "EmojiReact",
|
||||||
"object" => object_id,
|
"object" => object_id,
|
||||||
"actor" => _actor,
|
"actor" => _actor,
|
||||||
"id" => id,
|
"id" => id,
|
||||||
|
@ -751,7 +751,7 @@ def handle_incoming(
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{
|
%{
|
||||||
"type" => "Undo",
|
"type" => "Undo",
|
||||||
"object" => %{"type" => "EmojiReaction", "id" => reaction_activity_id},
|
"object" => %{"type" => "EmojiReact", "id" => reaction_activity_id},
|
||||||
"actor" => _actor,
|
"actor" => _actor,
|
||||||
"id" => id
|
"id" => id
|
||||||
} = data,
|
} = data,
|
||||||
|
|
|
@ -308,7 +308,7 @@ def make_like_data(
|
||||||
|
|
||||||
def make_emoji_reaction_data(user, object, emoji, activity_id) do
|
def make_emoji_reaction_data(user, object, emoji, activity_id) do
|
||||||
make_like_data(user, object, activity_id)
|
make_like_data(user, object, activity_id)
|
||||||
|> Map.put("type", "EmojiReaction")
|
|> Map.put("type", "EmojiReact")
|
||||||
|> Map.put("content", emoji)
|
|> Map.put("content", emoji)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -490,10 +490,19 @@ def fetch_latest_follow(%User{ap_id: follower_id}, %User{ap_id: followed_id}) do
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fetch_latest_undo(%User{ap_id: ap_id}) do
|
||||||
|
"Undo"
|
||||||
|
|> Activity.Queries.by_type()
|
||||||
|
|> where(actor: ^ap_id)
|
||||||
|
|> order_by([activity], fragment("? desc nulls last", activity.id))
|
||||||
|
|> limit(1)
|
||||||
|
|> Repo.one()
|
||||||
|
end
|
||||||
|
|
||||||
def get_latest_reaction(internal_activity_id, %{ap_id: ap_id}, emoji) do
|
def get_latest_reaction(internal_activity_id, %{ap_id: ap_id}, emoji) do
|
||||||
%{data: %{"object" => object_ap_id}} = Activity.get_by_id(internal_activity_id)
|
%{data: %{"object" => object_ap_id}} = Activity.get_by_id(internal_activity_id)
|
||||||
|
|
||||||
"EmojiReaction"
|
"EmojiReact"
|
||||||
|> Activity.Queries.by_type()
|
|> Activity.Queries.by_type()
|
||||||
|> where(actor: ^ap_id)
|
|> where(actor: ^ap_id)
|
||||||
|> where([activity], fragment("?->>'content' = ?", activity.data, ^emoji))
|
|> where([activity], fragment("?->>'content' = ?", activity.data, ^emoji))
|
||||||
|
|
|
@ -97,7 +97,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
plug(
|
plug(
|
||||||
OAuthScopesPlug,
|
OAuthScopesPlug,
|
||||||
%{scopes: ["read"], admin: true}
|
%{scopes: ["read"], admin: true}
|
||||||
when action in [:config_show, :migrate_from_db, :list_log]
|
when action in [:config_show, :list_log]
|
||||||
)
|
)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
|
@ -793,33 +793,13 @@ def config_descriptions(conn, _params) do
|
||||||
|> Plug.Conn.send_resp(200, @descriptions_json)
|
|> Plug.Conn.send_resp(200, @descriptions_json)
|
||||||
end
|
end
|
||||||
|
|
||||||
def migrate_from_db(conn, _params) do
|
|
||||||
with :ok <- configurable_from_database(conn) do
|
|
||||||
Mix.Tasks.Pleroma.Config.run([
|
|
||||||
"migrate_from_db",
|
|
||||||
"--env",
|
|
||||||
to_string(Pleroma.Config.get(:env)),
|
|
||||||
"-d"
|
|
||||||
])
|
|
||||||
|
|
||||||
json(conn, %{})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def config_show(conn, %{"only_db" => true}) do
|
def config_show(conn, %{"only_db" => true}) do
|
||||||
with :ok <- configurable_from_database(conn) do
|
with :ok <- configurable_from_database(conn) do
|
||||||
configs = Pleroma.Repo.all(ConfigDB)
|
configs = Pleroma.Repo.all(ConfigDB)
|
||||||
|
|
||||||
if configs == [] do
|
conn
|
||||||
errors(
|
|> put_view(ConfigView)
|
||||||
conn,
|
|> render("index.json", %{configs: configs})
|
||||||
{:error, "To use configuration from database migrate your settings to database."}
|
|
||||||
)
|
|
||||||
else
|
|
||||||
conn
|
|
||||||
|> put_view(ConfigView)
|
|
||||||
|> render("index.json", %{configs: configs})
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -827,45 +807,38 @@ def config_show(conn, _params) do
|
||||||
with :ok <- configurable_from_database(conn) do
|
with :ok <- configurable_from_database(conn) do
|
||||||
configs = ConfigDB.get_all_as_keyword()
|
configs = ConfigDB.get_all_as_keyword()
|
||||||
|
|
||||||
if configs == [] do
|
merged =
|
||||||
errors(
|
Pleroma.Config.Holder.config()
|
||||||
conn,
|
|> ConfigDB.merge(configs)
|
||||||
{:error, "To use configuration from database migrate your settings to database."}
|
|> Enum.map(fn {group, values} ->
|
||||||
)
|
Enum.map(values, fn {key, value} ->
|
||||||
else
|
db =
|
||||||
merged =
|
if configs[group][key] do
|
||||||
Pleroma.Config.Holder.config()
|
ConfigDB.get_db_keys(configs[group][key], key)
|
||||||
|> ConfigDB.merge(configs)
|
end
|
||||||
|> Enum.map(fn {group, values} ->
|
|
||||||
Enum.map(values, fn {key, value} ->
|
|
||||||
db =
|
|
||||||
if configs[group][key] do
|
|
||||||
ConfigDB.get_db_keys(configs[group][key], key)
|
|
||||||
end
|
|
||||||
|
|
||||||
db_value = configs[group][key]
|
db_value = configs[group][key]
|
||||||
|
|
||||||
merged_value =
|
merged_value =
|
||||||
if !is_nil(db_value) and Keyword.keyword?(db_value) and
|
if !is_nil(db_value) and Keyword.keyword?(db_value) and
|
||||||
ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do
|
ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do
|
||||||
ConfigDB.merge_group(group, key, value, db_value)
|
ConfigDB.merge_group(group, key, value, db_value)
|
||||||
else
|
else
|
||||||
value
|
value
|
||||||
end
|
end
|
||||||
|
|
||||||
setting = %{
|
setting = %{
|
||||||
group: ConfigDB.convert(group),
|
group: ConfigDB.convert(group),
|
||||||
key: ConfigDB.convert(key),
|
key: ConfigDB.convert(key),
|
||||||
value: ConfigDB.convert(merged_value)
|
value: ConfigDB.convert(merged_value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if db, do: Map.put(setting, :db, db), else: setting
|
if db, do: Map.put(setting, :db, db), else: setting
|
||||||
end)
|
|
||||||
end)
|
end)
|
||||||
|> List.flatten()
|
end)
|
||||||
|
|> List.flatten()
|
||||||
|
|
||||||
json(conn, %{configs: merged})
|
json(conn, %{configs: merged})
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -890,17 +863,36 @@ def config_update(conn, %{"configs" => configs}) do
|
||||||
Ecto.get_meta(config, :state) == :deleted
|
Ecto.get_meta(config, :state) == :deleted
|
||||||
end)
|
end)
|
||||||
|
|
||||||
Pleroma.Config.TransferTask.load_and_update_env(deleted)
|
Pleroma.Config.TransferTask.load_and_update_env(deleted, false)
|
||||||
|
|
||||||
Mix.Tasks.Pleroma.Config.run([
|
need_reboot? =
|
||||||
"migrate_from_db",
|
Enum.any?(updated, fn config ->
|
||||||
"--env",
|
group = ConfigDB.from_string(config.group)
|
||||||
to_string(Pleroma.Config.get(:env))
|
key = ConfigDB.from_string(config.key)
|
||||||
])
|
value = ConfigDB.from_binary(config.value)
|
||||||
|
Pleroma.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
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(ConfigView)
|
|> put_view(ConfigView)
|
||||||
|> render("index.json", %{configs: updated})
|
|> render("index.json", response)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
json(conn, %{})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,16 @@
|
||||||
defmodule Pleroma.Web.AdminAPI.ConfigView do
|
defmodule Pleroma.Web.AdminAPI.ConfigView do
|
||||||
use Pleroma.Web, :view
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
def render("index.json", %{configs: configs}) do
|
def render("index.json", %{configs: configs} = params) do
|
||||||
%{
|
map = %{
|
||||||
configs: render_many(configs, __MODULE__, "show.json", as: :config)
|
configs: render_many(configs, __MODULE__, "show.json", as: :config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if params[:need_reboot] do
|
||||||
|
Map.put(map, :need_reboot, true)
|
||||||
|
else
|
||||||
|
map
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def render("show.json", %{config: config}) do
|
def render("show.json", %{config: config}) do
|
||||||
|
|
|
@ -315,8 +315,9 @@ def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
|
||||||
with %Activity{
|
with %Activity{
|
||||||
actor: ^user_ap_id,
|
actor: ^user_ap_id,
|
||||||
data: %{"type" => "Create"},
|
data: %{"type" => "Create"},
|
||||||
object: %Object{data: %{"type" => "Note"}}
|
object: %Object{data: %{"type" => object_type}}
|
||||||
} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||||
|
true <- object_type in ["Note", "Article", "Question"],
|
||||||
true <- Visibility.is_public?(activity),
|
true <- Visibility.is_public?(activity),
|
||||||
{:ok, _user} <- User.add_pinnned_activity(user, activity) do
|
{:ok, _user} <- User.add_pinnned_activity(user, activity) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
|
|
|
@ -179,9 +179,9 @@ def make_poll_data(%{"poll" => %{"options" => options, "expires_in" => expires_i
|
||||||
end)
|
end)
|
||||||
|
|
||||||
end_time =
|
end_time =
|
||||||
NaiveDateTime.utc_now()
|
DateTime.utc_now()
|
||||||
|> NaiveDateTime.add(expires_in)
|
|> DateTime.add(expires_in)
|
||||||
|> NaiveDateTime.to_iso8601()
|
|> DateTime.to_iso8601()
|
||||||
|
|
||||||
key = if truthy_param?(data["poll"]["multiple"]), do: "anyOf", else: "oneOf"
|
key = if truthy_param?(data["poll"]["multiple"]), do: "anyOf", else: "oneOf"
|
||||||
poll = %{"type" => "Question", key => option_notes, "closed" => end_time}
|
poll = %{"type" => "Question", key => option_notes, "closed" => end_time}
|
||||||
|
|
|
@ -61,7 +61,17 @@ defmodule Pleroma.Web.Endpoint do
|
||||||
plug(Plug.RequestId)
|
plug(Plug.RequestId)
|
||||||
plug(Plug.Logger, log: :debug)
|
plug(Plug.Logger, log: :debug)
|
||||||
|
|
||||||
plug(Pleroma.Plugs.Parsers)
|
plug(Plug.Parsers,
|
||||||
|
parsers: [
|
||||||
|
:urlencoded,
|
||||||
|
{:multipart, length: {Pleroma.Config, :get, [[:instance, :upload_limit]]}},
|
||||||
|
:json
|
||||||
|
],
|
||||||
|
pass: ["*/*"],
|
||||||
|
json_decoder: Jason,
|
||||||
|
length: Pleroma.Config.get([:instance, :upload_limit]),
|
||||||
|
body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []}
|
||||||
|
)
|
||||||
|
|
||||||
plug(Plug.MethodOverride)
|
plug(Plug.MethodOverride)
|
||||||
plug(Plug.Head)
|
plug(Plug.Head)
|
||||||
|
|
|
@ -67,7 +67,7 @@ def render("relationships.json", %{user: user, targets: targets}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_render("show.json", %{user: user} = opts) do
|
defp do_render("show.json", %{user: user} = opts) do
|
||||||
display_name = HTML.strip_tags(user.name || user.nickname)
|
display_name = user.name || user.nickname
|
||||||
|
|
||||||
image = User.avatar_url(user) |> MediaProxy.url()
|
image = User.avatar_url(user) |> MediaProxy.url()
|
||||||
header = User.banner_url(user) |> MediaProxy.url()
|
header = User.banner_url(user) |> MediaProxy.url()
|
||||||
|
@ -105,7 +105,7 @@ defp do_render("show.json", %{user: user} = opts) do
|
||||||
|> User.fields()
|
|> User.fields()
|
||||||
|> Enum.map(fn %{"name" => name, "value" => value} ->
|
|> Enum.map(fn %{"name" => name, "value" => value} ->
|
||||||
%{
|
%{
|
||||||
"name" => Pleroma.HTML.strip_tags(name),
|
"name" => name,
|
||||||
"value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
|
"value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
defmodule Pleroma.Web.MastodonAPI.PollView do
|
defmodule Pleroma.Web.MastodonAPI.PollView do
|
||||||
use Pleroma.Web, :view
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
alias Pleroma.HTML
|
|
||||||
alias Pleroma.Web.CommonAPI.Utils
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
|
|
||||||
def render("show.json", %{object: object, multiple: multiple, options: options} = params) do
|
def render("show.json", %{object: object, multiple: multiple, options: options} = params) do
|
||||||
|
@ -57,7 +56,7 @@ defp options_and_votes_count(options) do
|
||||||
current_count = option["replies"]["totalItems"] || 0
|
current_count = option["replies"]["totalItems"] || 0
|
||||||
|
|
||||||
{%{
|
{%{
|
||||||
title: HTML.strip_tags(name),
|
title: name,
|
||||||
votes_count: current_count
|
votes_count: current_count
|
||||||
}, current_count + count}
|
}, current_count + count}
|
||||||
end)
|
end)
|
||||||
|
|
|
@ -216,21 +216,6 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
|
|
||||||
summary = object.data["summary"] || ""
|
summary = object.data["summary"] || ""
|
||||||
|
|
||||||
summary_html =
|
|
||||||
summary
|
|
||||||
|> HTML.get_cached_scrubbed_html_for_activity(
|
|
||||||
User.html_filter_policy(opts[:for]),
|
|
||||||
activity,
|
|
||||||
"mastoapi:summary"
|
|
||||||
)
|
|
||||||
|
|
||||||
summary_plaintext =
|
|
||||||
summary
|
|
||||||
|> HTML.get_cached_stripped_html_for_activity(
|
|
||||||
activity,
|
|
||||||
"mastoapi:summary"
|
|
||||||
)
|
|
||||||
|
|
||||||
card = render("card.json", Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity))
|
card = render("card.json", Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity))
|
||||||
|
|
||||||
url =
|
url =
|
||||||
|
@ -256,7 +241,11 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
emoji_reactions =
|
emoji_reactions =
|
||||||
with %{data: %{"reactions" => emoji_reactions}} <- object do
|
with %{data: %{"reactions" => emoji_reactions}} <- object do
|
||||||
Enum.map(emoji_reactions, fn [emoji, users] ->
|
Enum.map(emoji_reactions, fn [emoji, users] ->
|
||||||
%{emoji: emoji, count: length(users)}
|
%{
|
||||||
|
emoji: emoji,
|
||||||
|
count: length(users),
|
||||||
|
reacted: !!(opts[:for] && opts[:for].ap_id in users)
|
||||||
|
}
|
||||||
end)
|
end)
|
||||||
else
|
else
|
||||||
_ -> []
|
_ -> []
|
||||||
|
@ -282,7 +271,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
muted: thread_muted? || User.mutes?(opts[:for], user),
|
muted: thread_muted? || User.mutes?(opts[:for], user),
|
||||||
pinned: pinned?(activity, user),
|
pinned: pinned?(activity, user),
|
||||||
sensitive: sensitive,
|
sensitive: sensitive,
|
||||||
spoiler_text: summary_html,
|
spoiler_text: summary,
|
||||||
visibility: get_visibility(object),
|
visibility: get_visibility(object),
|
||||||
media_attachments: attachments,
|
media_attachments: attachments,
|
||||||
poll: render(PollView, "show.json", object: object, for: opts[:for]),
|
poll: render(PollView, "show.json", object: object, for: opts[:for]),
|
||||||
|
@ -299,7 +288,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
conversation_id: get_context_id(activity),
|
conversation_id: get_context_id(activity),
|
||||||
in_reply_to_account_acct: reply_to_user && reply_to_user.nickname,
|
in_reply_to_account_acct: reply_to_user && reply_to_user.nickname,
|
||||||
content: %{"text/plain" => content_plaintext},
|
content: %{"text/plain" => content_plaintext},
|
||||||
spoiler_text: %{"text/plain" => summary_plaintext},
|
spoiler_text: %{"text/plain" => summary},
|
||||||
expires_at: expires_at,
|
expires_at: expires_at,
|
||||||
direct_conversation_id: direct_conversation_id,
|
direct_conversation_id: direct_conversation_id,
|
||||||
thread_muted: thread_muted?,
|
thread_muted: thread_muted?,
|
||||||
|
|
|
@ -573,11 +573,14 @@ def update_file(conn, %{"action" => action}) do
|
||||||
assumed to be emojis and stored in the new `pack.json` file.
|
assumed to be emojis and stored in the new `pack.json` file.
|
||||||
"""
|
"""
|
||||||
def import_from_fs(conn, _params) do
|
def import_from_fs(conn, _params) do
|
||||||
with {:ok, results} <- File.ls(emoji_dir_path()) do
|
emoji_path = emoji_dir_path()
|
||||||
|
|
||||||
|
with {:ok, %{access: :read_write}} <- File.stat(emoji_path),
|
||||||
|
{:ok, results} <- File.ls(emoji_path) do
|
||||||
imported_pack_names =
|
imported_pack_names =
|
||||||
results
|
results
|
||||||
|> Enum.filter(fn file ->
|
|> Enum.filter(fn file ->
|
||||||
dir_path = Path.join(emoji_dir_path(), file)
|
dir_path = Path.join(emoji_path, file)
|
||||||
# Find the directories that do NOT have pack.json
|
# Find the directories that do NOT have pack.json
|
||||||
File.dir?(dir_path) and not File.exists?(Path.join(dir_path, "pack.json"))
|
File.dir?(dir_path) and not File.exists?(Path.join(dir_path, "pack.json"))
|
||||||
end)
|
end)
|
||||||
|
@ -585,6 +588,11 @@ def import_from_fs(conn, _params) do
|
||||||
|
|
||||||
json(conn, imported_pack_names)
|
json(conn, imported_pack_names)
|
||||||
else
|
else
|
||||||
|
{:ok, %{access: _}} ->
|
||||||
|
conn
|
||||||
|
|> put_status(:internal_server_error)
|
||||||
|
|> json(%{error: "Error: emoji pack directory must be writable"})
|
||||||
|
|
||||||
{:error, _} ->
|
{:error, _} ->
|
||||||
conn
|
conn
|
||||||
|> put_status(:internal_server_error)
|
|> put_status(:internal_server_error)
|
||||||
|
|
|
@ -47,13 +47,16 @@ def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id})
|
||||||
Object.normalize(activity) do
|
Object.normalize(activity) do
|
||||||
reactions =
|
reactions =
|
||||||
emoji_reactions
|
emoji_reactions
|
||||||
|> Enum.map(fn [emoji, users] ->
|
|> Enum.map(fn [emoji, user_ap_ids] ->
|
||||||
users = Enum.map(users, &User.get_cached_by_ap_id/1)
|
users =
|
||||||
|
Enum.map(user_ap_ids, &User.get_cached_by_ap_id/1)
|
||||||
|
|> Enum.filter(& &1)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
emoji: emoji,
|
emoji: emoji,
|
||||||
count: length(users),
|
count: length(users),
|
||||||
accounts: AccountView.render("index.json", %{users: users, for: user, as: :user})
|
accounts: AccountView.render("index.json", %{users: users, for: user, as: :user}),
|
||||||
|
reacted: !!(user && user.ap_id in user_ap_ids)
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,6 @@ defp maybe_put_title(meta, html) when meta != %{} do
|
||||||
defp maybe_put_title(meta, _), do: meta
|
defp maybe_put_title(meta, _), do: meta
|
||||||
|
|
||||||
defp get_page_title(html) do
|
defp get_page_title(html) do
|
||||||
Floki.find(html, "title") |> List.first() |> Floki.text()
|
Floki.find(html, "html head title") |> List.first() |> Floki.text()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -196,7 +196,7 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/config", AdminAPIController, :config_show)
|
get("/config", AdminAPIController, :config_show)
|
||||||
post("/config", AdminAPIController, :config_update)
|
post("/config", AdminAPIController, :config_update)
|
||||||
get("/config/descriptions", AdminAPIController, :config_descriptions)
|
get("/config/descriptions", AdminAPIController, :config_descriptions)
|
||||||
get("/config/migrate_from_db", AdminAPIController, :migrate_from_db)
|
get("/restart", AdminAPIController, :restart)
|
||||||
|
|
||||||
get("/moderation_log", AdminAPIController, :list_log)
|
get("/moderation_log", AdminAPIController, :list_log)
|
||||||
|
|
||||||
|
|
|
@ -138,7 +138,8 @@ defp should_send?(%User{} = user, %Activity{} = item) do
|
||||||
|
|
||||||
with parent <- Object.normalize(item) || item,
|
with parent <- Object.normalize(item) || item,
|
||||||
true <-
|
true <-
|
||||||
Enum.all?([blocked_ap_ids, muted_ap_ids, reblog_muted_ap_ids], &(item.actor not in &1)),
|
Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)),
|
||||||
|
true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids,
|
||||||
true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(parent.data["actor"] not in &1)),
|
true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(parent.data["actor"] not in &1)),
|
||||||
true <- MapSet.disjoint?(recipients, recipient_blocks),
|
true <- MapSet.disjoint?(recipients, recipient_blocks),
|
||||||
%{host: item_host} <- URI.parse(item.actor),
|
%{host: item_host} <- URI.parse(item.actor),
|
||||||
|
|
10
mix.exs
10
mix.exs
|
@ -8,7 +8,7 @@ def project do
|
||||||
elixir: "~> 1.8",
|
elixir: "~> 1.8",
|
||||||
elixirc_paths: elixirc_paths(Mix.env()),
|
elixirc_paths: elixirc_paths(Mix.env()),
|
||||||
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
|
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
|
||||||
elixirc_options: [warnings_as_errors: true],
|
elixirc_options: [warnings_as_errors: warnings_as_errors(Mix.env())],
|
||||||
xref: [exclude: [:eldap]],
|
xref: [exclude: [:eldap]],
|
||||||
start_permanent: Mix.env() == :prod,
|
start_permanent: Mix.env() == :prod,
|
||||||
aliases: aliases(),
|
aliases: aliases(),
|
||||||
|
@ -73,6 +73,11 @@ defp elixirc_paths(:benchmark), do: ["lib", "benchmarks"]
|
||||||
defp elixirc_paths(:test), do: ["lib", "test/support"]
|
defp elixirc_paths(:test), do: ["lib", "test/support"]
|
||||||
defp elixirc_paths(_), do: ["lib"]
|
defp elixirc_paths(_), do: ["lib"]
|
||||||
|
|
||||||
|
defp warnings_as_errors(:prod), do: false
|
||||||
|
# Uncomment this if you need testing configurable_from_database logic
|
||||||
|
# defp warnings_as_errors(:dev), do: false
|
||||||
|
defp warnings_as_errors(_), do: true
|
||||||
|
|
||||||
# Specifies OAuth dependencies.
|
# Specifies OAuth dependencies.
|
||||||
defp oauth_deps do
|
defp oauth_deps do
|
||||||
oauth_strategy_packages =
|
oauth_strategy_packages =
|
||||||
|
@ -166,7 +171,8 @@ defp deps do
|
||||||
{:captcha,
|
{:captcha,
|
||||||
git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git",
|
git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git",
|
||||||
ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"},
|
ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"},
|
||||||
{:mox, "~> 0.5", only: :test}
|
{:mox, "~> 0.5", only: :test},
|
||||||
|
{:restarter, path: "./restarter"}
|
||||||
] ++ oauth_deps()
|
] ++ oauth_deps()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
2
mix.lock
2
mix.lock
|
@ -77,7 +77,7 @@
|
||||||
"phoenix_html": {:hex, :phoenix_html, "2.13.3", "850e292ff6e204257f5f9c4c54a8cb1f6fbc16ed53d360c2b780a3d0ba333867", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
"phoenix_html": {:hex, :phoenix_html, "2.13.3", "850e292ff6e204257f5f9c4c54a8cb1f6fbc16ed53d360c2b780a3d0ba333867", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm"},
|
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm"},
|
||||||
"phoenix_swoosh": {:hex, :phoenix_swoosh, "0.2.0", "a7e0b32077cd6d2323ae15198839b05d9caddfa20663fd85787479e81f89520e", [:mix], [{:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 0.1", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm"},
|
"phoenix_swoosh": {:hex, :phoenix_swoosh, "0.2.0", "a7e0b32077cd6d2323ae15198839b05d9caddfa20663fd85787479e81f89520e", [:mix], [{:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 0.1", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"plug": {:hex, :plug, "1.8.3", "12d5f9796dc72e8ac9614e94bda5e51c4c028d0d428e9297650d09e15a684478", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"},
|
"plug": {:hex, :plug, "1.9.0", "8d7c4e26962283ff9f8f3347bd73838e2413fbc38b7bb5467d5924f68f3a5a4a", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
"plug_cowboy": {:hex, :plug_cowboy, "2.1.0", "b75768153c3a8a9e8039d4b25bb9b14efbc58e9c4a6e6a270abff1cd30cbe320", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
"plug_cowboy": {:hex, :plug_cowboy, "2.1.0", "b75768153c3a8a9e8039d4b25bb9b14efbc58e9c4a6e6a270abff1cd30cbe320", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
|
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
|
||||||
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,126 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<metadata>Copyright (C) 2019 by original authors @ fontello.com</metadata>
|
||||||
|
<defs>
|
||||||
|
<font id="fontello" horiz-adv-x="1000" >
|
||||||
|
<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="857" descent="-143" />
|
||||||
|
<missing-glyph horiz-adv-x="1000" />
|
||||||
|
<glyph glyph-name="cancel" unicode="" d="M724 119q0-22-15-38l-76-76q-16-15-38-15t-38 15l-164 165-164-165q-16-15-38-15t-38 15l-76 76q-16 16-16 38t16 38l164 164-164 164q-16 16-16 38t16 38l76 76q16 16 38 16t38-16l164-164 164 164q16 16 38 16t38-16l76-76q15-15 15-38t-15-38l-164-164 164-164q15-15 15-38z" horiz-adv-x="785.7" />
|
||||||
|
|
||||||
|
<glyph glyph-name="upload" unicode="" d="M714 36q0 14-10 25t-25 10-25-10-11-25 11-25 25-11 25 11 10 25z m143 0q0 14-10 25t-26 10-25-10-10-25 10-25 25-11 26 11 10 25z m72 125v-179q0-22-16-38t-38-16h-821q-23 0-38 16t-16 38v179q0 22 16 38t38 15h238q12-31 39-51t62-20h143q34 0 61 20t40 51h238q22 0 38-15t16-38z m-182 361q-9-22-33-22h-143v-250q0-15-10-25t-25-11h-143q-15 0-25 11t-11 25v250h-143q-23 0-33 22-9 22 8 39l250 250q10 10 25 10t25-10l250-250q18-17 8-39z" horiz-adv-x="928.6" />
|
||||||
|
|
||||||
|
<glyph glyph-name="star" unicode="" d="M929 496q0-12-15-27l-202-197 48-279q0-4 0-12 0-11-6-19t-17-9q-10 0-22 7l-251 132-250-132q-12-7-23-7-11 0-17 9t-6 19q0 4 1 12l48 279-203 197q-14 15-14 27 0 21 31 26l280 40 126 254q11 23 27 23t28-23l125-254 280-40q32-5 32-26z" horiz-adv-x="928.6" />
|
||||||
|
|
||||||
|
<glyph glyph-name="star-empty" unicode="" d="M635 297l170 166-235 34-106 213-105-213-236-34 171-166-41-235 211 111 211-111z m294 199q0-12-15-27l-202-197 48-279q0-4 0-12 0-28-23-28-10 0-22 7l-251 132-250-132q-12-7-23-7-11 0-17 9t-6 19q0 4 1 12l48 279-203 197q-14 15-14 27 0 21 31 26l280 40 126 254q11 23 27 23t28-23l125-254 280-40q32-5 32-26z" horiz-adv-x="928.6" />
|
||||||
|
|
||||||
|
<glyph glyph-name="retweet" unicode="" d="M714 18q0-7-5-13t-13-5h-535q-5 0-8 1t-5 4-3 4-2 7 0 6v335h-107q-15 0-25 11t-11 25q0 13 8 23l179 214q11 12 27 12t28-12l178-214q9-10 9-23 0-15-11-25t-25-11h-107v-214h321q9 0 14-6l89-108q4-5 4-11z m357 232q0-13-8-23l-178-214q-12-13-28-13t-27 13l-179 214q-8 10-8 23 0 14 11 25t25 11h107v214h-322q-9 0-14 7l-89 107q-4 5-4 11 0 7 5 12t13 6h536q4 0 7-1t5-4 3-5 2-6 1-7v-334h107q14 0 25-11t10-25z" horiz-adv-x="1071.4" />
|
||||||
|
|
||||||
|
<glyph glyph-name="eye-off" unicode="" d="M310 112l43 79q-48 35-76 88t-27 114q0 67 34 125-128-65-213-197 94-144 239-209z m217 424q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-11 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m202 106q0-4 0-5-59-105-176-316t-176-316l-28-50q-5-9-15-9-7 0-75 39-9 6-9 16 0 7 25 49-80 36-147 96t-117 137q-11 17-11 38t11 39q86 131 212 207t277 76q50 0 100-10l31 54q5 9 15 9 3 0 10-3t18-9 18-10 18-10 10-7q9-5 9-15z m21-249q0-78-44-142t-117-91l157 280q4-25 4-47z m250-72q0-19-11-38-22-36-61-81-84-96-194-149t-234-53l41 74q119 10 219 76t169 171q-65 100-158 164l35 63q53-36 102-85t81-103q11-19 11-39z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="search" unicode="" d="M643 393q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-22-50t-50-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
|
||||||
|
|
||||||
|
<glyph glyph-name="cog" unicode="" d="M571 357q0 59-41 101t-101 42-101-42-42-101 42-101 101-42 101 42 41 101z m286 61v-124q0-7-4-13t-11-7l-104-16q-10-30-21-51 19-27 59-77 6-6 6-13t-5-13q-15-21-55-61t-53-39q-7 0-14 5l-77 60q-25-13-51-21-9-76-16-104-4-16-20-16h-124q-8 0-14 5t-6 12l-16 103q-27 9-50 21l-79-60q-6-5-14-5-8 0-14 6-70 64-92 94-4 5-4 13 0 6 5 12 8 12 28 37t30 40q-15 28-23 55l-102 15q-7 1-11 7t-5 13v124q0 7 5 13t10 7l104 16q8 25 22 51-23 32-60 77-6 7-6 14 0 5 5 12 15 20 55 60t53 40q7 0 15-5l77-60q24 13 50 21 9 76 17 104 3 16 20 16h124q7 0 13-5t7-12l15-103q28-9 51-20l79 59q5 5 13 5 7 0 14-5 72-67 92-95 4-5 4-12 0-7-4-13-9-12-29-37t-30-40q15-28 23-54l102-16q7-1 12-7t4-13z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="logout" unicode="" d="M357 53q0-2 1-11t0-14-2-14-5-10-12-4h-178q-67 0-114 47t-47 114v392q0 67 47 114t114 47h178q8 0 13-5t5-13q0-2 1-11t0-15-2-13-5-11-12-3h-178q-37 0-63-26t-27-64v-392q0-37 27-63t63-27h174t6 0 7-2 4-3 4-5 1-8z m518 304q0-14-11-25l-303-304q-11-10-25-10t-25 10-11 25v161h-250q-14 0-25 11t-11 25v214q0 15 11 25t25 11h250v161q0 14 11 25t25 10 25-10l303-304q11-10 11-25z" horiz-adv-x="928.6" />
|
||||||
|
|
||||||
|
<glyph glyph-name="down-open" unicode="" d="M939 406l-414-413q-10-11-25-11t-25 11l-414 413q-11 11-11 26t11 25l93 92q10 11 25 11t25-11l296-296 296 296q11 11 25 11t26-11l92-92q11-11 11-25t-11-26z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="attach" unicode="" d="M244-133q-102 0-170 72-72 70-74 166t84 190l496 496q80 80 174 54 44-12 79-47t47-79q26-96-54-176l-474-474q-40-40-88-46-48-4-80 28-30 24-27 74t47 92l332 334q24 26 50 0t0-50l-332-332q-44-44-20-70 12-8 24-6 24 4 46 26l474 474q50 50 34 108-16 60-76 76-54 14-108-36l-494-494q-66-76-64-143t52-117q50-48 117-50t141 62l496 494q24 24 50 0 26-22 0-48l-496-496q-82-82-186-82z" horiz-adv-x="939" />
|
||||||
|
|
||||||
|
<glyph glyph-name="picture" unicode="" d="M357 536q0-45-31-76t-76-32-76 32-31 76 31 76 76 31 76-31 31-76z m572-215v-250h-786v107l178 179 90-89 285 285z m53 393h-893q-7 0-12-5t-6-13v-678q0-7 6-13t12-5h893q7 0 13 5t5 13v678q0 8-5 13t-13 5z m89-18v-678q0-37-26-63t-63-27h-893q-36 0-63 27t-26 63v678q0 37 26 63t63 27h893q37 0 63-27t26-63z" horiz-adv-x="1071.4" />
|
||||||
|
|
||||||
|
<glyph glyph-name="video" unicode="" d="M214-36v72q0 14-10 25t-25 10h-72q-14 0-25-10t-11-25v-72q0-14 11-25t25-11h72q14 0 25 11t10 25z m0 214v72q0 14-10 25t-25 11h-72q-14 0-25-11t-11-25v-72q0-14 11-25t25-10h72q14 0 25 10t10 25z m0 215v71q0 15-10 25t-25 11h-72q-14 0-25-11t-11-25v-71q0-15 11-25t25-11h72q14 0 25 11t10 25z m572-429v286q0 14-11 25t-25 11h-429q-14 0-25-11t-10-25v-286q0-14 10-25t25-11h429q15 0 25 11t11 25z m-572 643v71q0 15-10 26t-25 10h-72q-14 0-25-10t-11-26v-71q0-14 11-25t25-11h72q14 0 25 11t10 25z m786-643v72q0 14-11 25t-25 10h-71q-15 0-25-10t-11-25v-72q0-14 11-25t25-11h71q15 0 25 11t11 25z m-214 429v285q0 15-11 26t-25 10h-429q-14 0-25-10t-10-26v-285q0-15 10-25t25-11h429q15 0 25 11t11 25z m214-215v72q0 14-11 25t-25 11h-71q-15 0-25-11t-11-25v-72q0-14 11-25t25-10h71q15 0 25 10t11 25z m0 215v71q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-71q0-15 11-25t25-11h71q15 0 25 11t11 25z m0 214v71q0 15-11 26t-25 10h-71q-15 0-25-10t-11-26v-71q0-14 11-25t25-11h71q15 0 25 11t11 25z m71 89v-750q0-37-26-63t-63-26h-893q-36 0-63 26t-26 63v750q0 37 26 63t63 27h893q37 0 63-27t26-63z" horiz-adv-x="1071.4" />
|
||||||
|
|
||||||
|
<glyph glyph-name="right-open" unicode="" d="M618 368l-414-415q-11-10-25-10t-25 10l-93 93q-11 11-11 25t11 25l296 297-296 296q-11 11-11 25t11 25l93 93q10 11 25 11t25-11l414-414q10-11 10-25t-10-25z" horiz-adv-x="714.3" />
|
||||||
|
|
||||||
|
<glyph glyph-name="left-open" unicode="" d="M654 689l-297-296 297-297q10-10 10-25t-10-25l-93-93q-11-10-25-10t-25 10l-414 415q-11 10-11 25t11 25l414 414q10 11 25 11t25-11l93-93q10-10 10-25t-10-25z" horiz-adv-x="714.3" />
|
||||||
|
|
||||||
|
<glyph glyph-name="up-open" unicode="" d="M939 114l-92-92q-11-10-26-10t-25 10l-296 297-296-297q-11-10-25-10t-25 10l-93 92q-11 11-11 26t11 25l414 414q11 10 25 10t25-10l414-414q11-11 11-25t-11-26z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="bell-ringing-o" unicode="" d="M498 857c-30 0-54-24-54-53 0-8 2-15 5-22-147-22-236-138-236-245 0-268-95-393-177-462 0-39 32-71 71-71h249c0-79 63-143 142-143s142 64 142 143h249c39 0 71 32 71 71-82 69-178 194-178 462 0 107-88 223-235 245 2 7 4 14 4 22 0 29-24 53-53 53z m-309-45c-81-74-118-170-118-275l71 0c0 89 28 162 95 223l-48 52z m617 0l-48-52c67-61 96-134 95-223l71 0c1 105-37 201-118 275z m-397-799c5 0 9-4 9-9 0-44 36-80 80-80 5 0 9-4 9-9s-4-9-9-9c-54 0-98 44-98 98 0 5 4 9 9 9z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="lock" unicode="" d="M179 428h285v108q0 59-42 101t-101 41-101-41-41-101v-108z m464-53v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v108q0 102 74 176t176 74 177-74 73-176v-108h18q23 0 38-15t16-38z" horiz-adv-x="642.9" />
|
||||||
|
|
||||||
|
<glyph glyph-name="globe" unicode="" d="M429 786q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m153-291q-2-1-6-5t-7-6q1 0 2 3t3 6 2 4q3 4 12 8 8 4 29 7 19 5 29-6-1 1 5 7t8 7q2 1 8 3t9 4l1 12q-7-1-10 4t-3 12q0-2-4-5 0 4-2 5t-7-1-5-1q-5 2-8 5t-5 9-2 8q-1 3-5 6t-5 6q-1 1-2 3t-1 4-3 3-3 1-4-3-4-5-2-3q-2 1-4 1t-2-1-3-1-3-2q-1-2-4-2t-5-1q8 3-1 6-5 2-9 2 6 2 5 6t-5 8h3q-1 2-5 5t-10 5-7 3q-5 3-19 5t-18 1q-3-4-3-6t2-8 2-7q1-3-3-7t-3-7q0-4 7-9t6-12q-2-4-9-9t-9-6q-3-5-1-11t6-9q1-1 1-2t-2-3-3-2-4-2l-1-1q-7-3-12 3t-7 15q-4 14-9 17-13 4-16-1-3 7-23 15-14 5-33 2 4 0 0 8-4 9-10 7 1 3 2 10t0 7q2 8 7 13 1 1 4 5t5 7 1 4q19-3 28 6 2 3 6 9t6 10q5 3 8 3t8-3 8-3q8-1 8 6t-4 11q7 0 2 10-2 4-5 5-6 2-15-3-4-2 2-4-1 0-6-6t-9-10-9 3q0 0-3 7t-5 8q-5 0-9-9 1 5-6 9t-14 4q11 7-4 15-4 3-12 3t-11-2q-2-4-3-7t3-4 6-3 6-2 5-2q8-6 5-8-1 0-5-2t-6-2-4-2q-1-3 0-8t-1-8q-3 3-5 10t-4 9q4-5-14-3l-5 0q-3 0-9-1t-12-1-7 5q-3 4 0 11 0 2 2 1-2 2-6 5t-6 5q-25-8-52-23 3 0 6 1 3 1 8 4t5 3q19 7 24 4l3 2q7-9 11-14-4 3-17 1-11-3-12-7 4-6 2-10-2 2-6 6t-8 6-8 3q-9 0-13-1-81-45-131-124 4-4 7-4 2-1 3-5t1-6 6 1q5-4 2-10 1 0 25-15 10-10 11-12 2-6-5-10-1 1-5 5t-5 2q-2-3 0-10t6-7q-4 0-5-9t-2-20 0-13l1-1q-2-6 3-19t12-11q-7-1 11-24 3-4 4-5 2-1 7-4t9-6 5-5q2-3 6-13t8-13q-2-3 5-11t6-13q-1 0-2-1t-1 0q2-4 9-8t8-7q1-2 1-6t2-6 4-1q2 11-13 35-8 13-9 16-2 2-4 8t-2 8q1 0 3 0t5-2 4-3 1-1q-1-4 1-10t7-10 10-11 6-7q4-4 8-11t0-8q5 0 11-5t10-11q3-5 4-15t3-13q1-4 5-8t7-5l9-5t7-3q3-2 10-6t12-7q6-2 9-2t8 1 8 2q8 1 16-8t12-12q20-10 30-6-1 0 1-4t4-9 5-8 3-5q3-3 10-8t10-8q4 2 4 5-1-5 4-11t10-6q8 2 8 18-17-8-27 10 0 0-2 3t-2 5-1 4 0 5 2 1q5 0 6 2t-1 7-2 8q-1 4-6 11t-7 8q-3-5-9-4t-9 5q0-1-1-3t-1-4q-7 0-8 0 1 2 1 10t2 13q1 2 3 6t5 9 2 7-3 5-9 1q-11 0-15-11-1-2-2-6t-2-6-5-4q-4-2-14-1t-13 3q-8 4-13 16t-5 20q0 6 1 15t2 14-3 14q2 1 5 5t5 6q2 1 3 1t3 0 2 1 1 3q0 1-2 2-1 1-2 1 4-1 16 1t15-1q9-6 12 1 0 1-1 6t0 7q3-15 16-5 2-1 9-3t9-2q2-1 4-3t3-3 3 0 5 4q5-8 7-13 6-23 10-25 4-2 6-1t3 5 0 8-1 7l-1 5v10l0 4q-8 2-10 7t0 10 9 10q0 1 4 2t9 4 7 4q12 11 8 20 4 0 6 5 0 0-2 2t-5 2-2 2q5 2 1 8 3 2 4 7t4 5q5-6 12-1 5 5 1 9 2 4 11 6t10 5q4-1 5 1t0 7 2 7q2 2 9 5t7 2l9 7q2 2 0 2 10-1 18 6 5 6-4 11 2 4-1 5t-9 4q2 0 7 0t5 1q9 5-3 9-10 2-24-7z m-91-490q115 21 195 106-1 2-7 2t-7 2q-10 4-13 5 1 4-1 7t-5 5-7 5-6 4q-1 1-4 3t-4 3-4 2-5 2-5-1l-2-1q-2 0-3-1t-3-2-2-1 0-2q-12 10-20 13-3 0-6 3t-6 4-6 0-6-3q-3-3-4-9t-1-7q-4 3 0 10t1 10q-1 3-6 2t-6-2-7-5-5-3-4-3-5-5q-2-2-4-6t-2-6q-1 2-7 3t-5 3q1-5 2-19t3-22q4-17-7-26-15-14-16-23-2-12 7-14 0-4-5-12t-4-12q0-3 2-9z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="brush" unicode="" d="M464 209q0-124-87-212t-210-87q-81 0-149 40 68 39 109 108t40 151q0 61 44 105t105 44 105-44 43-105z m415 562q32-32 32-79t-33-79l-318-318q-20 55-61 97t-97 62l318 318q32 32 79 32t80-33z" horiz-adv-x="928" />
|
||||||
|
|
||||||
|
<glyph glyph-name="attention" unicode="" d="M571 90v106q0 8-5 13t-12 5h-108q-7 0-12-5t-5-13v-106q0-8 5-13t12-6h108q7 0 12 6t5 13z m-1 208l10 257q0 6-5 10-7 6-14 6h-122q-6 0-14-6-5-4-5-12l9-255q0-5 6-9t13-3h103q8 0 14 3t5 9z m-7 522l428-786q20-35-1-70-9-17-26-26t-35-10h-858q-18 0-35 10t-26 26q-21 35-1 70l429 786q9 17 26 27t36 10 36-10 27-27z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="plus" unicode="" d="M786 446v-107q0-22-16-38t-38-15h-232v-233q0-22-16-37t-38-16h-107q-22 0-38 16t-15 37v233h-232q-23 0-38 15t-16 38v107q0 23 16 38t38 16h232v232q0 22 15 38t38 16h107q23 0 38-16t16-38v-232h232q23 0 38-16t16-38z" horiz-adv-x="785.7" />
|
||||||
|
|
||||||
|
<glyph glyph-name="adjust" unicode="" d="M429 53v608q-83 0-153-41t-110-111-41-152 41-152 110-111 153-41z m428 304q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="edit" unicode="" d="M496 196l64 65-85 85-64-65v-31h53v-54h32z m245 402q-9 9-18 0l-196-196q-9-9 0-18t18 0l196 196q9 9 0 18z m45-331v-106q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h464q35 0 65-14 9-4 10-13 2-10-5-16l-27-28q-8-8-18-4-13 3-25 3h-464q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v70q0 7 5 12l36 36q8 8 20 4t11-16z m-54 411l161-160-375-375h-161v160z m248-73l-51-52-161 161 51 52q16 15 38 15t38-15l85-85q16-16 16-38t-16-38z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="pencil" unicode="" d="M203 0l50 51-131 131-51-51v-60h72v-71h60z m291 518q0 12-12 12-5 0-9-4l-303-302q-4-4-4-10 0-12 13-12 5 0 9 4l303 302q3 4 3 10z m-30 107l232-232-464-465h-232v233z m381-54q0-29-20-50l-93-93-232 233 93 92q20 21 50 21 29 0 51-21l131-131q20-22 20-51z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="pin" unicode="" d="M268 375v250q0 8-5 13t-13 5-13-5-5-13v-250q0-8 5-13t13-5 13 5 5 13z m375-197q0-14-11-25t-25-10h-239l-29-270q-1-7-6-11t-11-5h-1q-15 0-17 15l-43 271h-225q-15 0-25 10t-11 25q0 69 44 124t99 55v286q-29 0-50 21t-22 50 22 50 50 22h357q29 0 50-22t21-50-21-50-50-21v-286q55 0 99-55t44-124z" horiz-adv-x="642.9" />
|
||||||
|
|
||||||
|
<glyph glyph-name="wrench" unicode="" d="M214 36q0 14-10 25t-25 10-25-10-11-25 11-25 25-11 25 11 10 25z m360 234l-381-381q-21-20-50-20-29 0-51 20l-59 61q-21 20-21 50 0 29 21 51l380 380q22-55 64-97t97-64z m354 243q0-22-13-59-27-75-92-122t-144-46q-104 0-177 73t-73 177 73 176 177 74q32 0 67-10t60-26q9-6 9-15t-9-16l-163-94v-125l108-60q2 2 44 27t75 45 40 20q8 0 13-5t5-14z" horiz-adv-x="928.6" />
|
||||||
|
|
||||||
|
<glyph glyph-name="verified" unicode="" d="M926 453l-19 13c-21 14-30 41-23 65l6 22c10 34-13 69-48 75l-23 4c-25 4-45 23-49 48l-4 23c-6 35-41 57-75 47l-22-7c-24-7-51 2-65 22l-14 20c-21 29-62 33-88 9l-17-16c-19-17-46-21-69-8l-20 11c-31 17-70 3-84-30l-9-22c-9-24-33-39-58-37l-23 1c-36 2-65-28-62-63l2-23c2-25-13-49-36-59l-21-9c-33-14-46-53-29-84l12-20c13-22 10-50-7-69l-15-17c-24-27-19-68 11-88l19-13c21-14 30-41 23-65l-9-23c-10-34 13-69 48-75l23-4c25-4 45-23 49-48l4-23c6-35 41-57 75-47l22 7c24 7 51-2 65-22l14-19c21-29 62-33 88-9l17 16c19 17 46 21 69 8l20-11c31-17 70-3 84 30l9 22c9 24 33 39 58 37l23-1c36-2 65 28 62 63l-1 23c-2 25 13 49 36 59l21 9c33 14 46 53 29 84l-12 20c-13 22-10 50 7 69l15 17c25 26 20 68-9 88z m-399-189l-82-81-81 82-78 79 82 81 78-79 187 186 81-82-187-186z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="chart-bar" unicode="" d="M357 357v-286h-143v286h143z m214 286v-572h-142v572h142z m572-643v-72h-1143v858h71v-786h1072z m-357 500v-429h-143v429h143z m214 214v-643h-143v643h143z" horiz-adv-x="1142.9" />
|
||||||
|
|
||||||
|
<glyph glyph-name="users" unicode="" d="M331 357q-90-3-148-71h-75q-45 0-77 22t-31 66q0 197 69 197 4 0 25-11t54-24 66-12q38 0 75 13-3-21-3-37 0-78 45-143z m598-355q0-67-41-106t-108-39h-488q-68 0-108 39t-41 106q0 29 2 57t8 61 14 61 24 54 35 45 48 30 62 11q6 0 24-12t41-26 59-27 76-12 75 12 60 27 41 26 24 12q34 0 62-11t47-30 35-45 24-54 15-61 8-61 2-57z m-572 712q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z m393-214q0-89-63-152t-151-62-152 62-63 152 63 151 152 63 151-63 63-151z m321-126q0-43-31-66t-77-22h-75q-57 68-147 71 45 65 45 143 0 16-3 37 37-13 74-13 33 0 67 12t54 24 24 11q69 0 69-197z m-71 340q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z" horiz-adv-x="1071.4" />
|
||||||
|
|
||||||
|
<glyph glyph-name="address-book" unicode="" d="M0-143l0 1000 1000 0 0-178-109 0 0-95 109 0 0-178-109 0 0-98 109 0 0-177-109 0 0-96 109 0 0-178-1000 0z m193 285l504 0 0 155-187 111q37 19 58 54t22 77q0 58-42 101t-103 43-102-43-42-101q0-43 22-77t58-54l-188-111 0-155z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="cog-alt" unicode="" d="M500 357q0 59-42 101t-101 42-101-42-42-101 42-101 101-42 101 42 42 101z m429-286q0 29-22 51t-50 21-50-21-21-51q0-29 21-50t50-21 51 21 21 50z m0 572q0 29-22 50t-50 21-50-21-21-50q0-30 21-51t50-21 51 21 21 51z m-215-235v-103q0-6-4-11t-8-6l-87-14q-6-19-18-42 19-27 50-64 4-6 4-11 0-7-4-11-12-17-46-50t-43-33q-7 0-12 4l-64 50q-21-11-43-17-6-60-13-87-4-13-17-13h-104q-6 0-11 4t-5 10l-13 85q-19 6-42 18l-66-50q-4-4-11-4-6 0-12 4-80 75-80 90 0 5 4 10 5 8 23 30t26 34q-13 24-20 46l-85 13q-5 1-9 5t-4 11v104q0 5 4 10t9 6l86 14q7 19 18 42-19 27-50 64-4 6-4 11 0 7 4 12 12 16 46 49t44 33q6 0 12-4l64-50q19 10 43 18 6 60 13 86 3 13 16 13h104q6 0 11-4t6-10l13-85q19-6 42-17l65 49q5 4 12 4 6 0 11-4 81-75 81-90 0-4-4-10-7-9-24-30t-25-34q13-27 19-46l85-12q6-2 9-6t4-11z m357-298v-78q0-9-83-17-6-15-16-29 28-63 28-77 0-2-2-4-68-40-69-40-5 0-26 27t-29 37q-11-1-17-1t-17 1q-7-11-29-37t-25-27q-1 0-69 40-3 2-3 4 0 14 29 77-10 14-17 29-83 8-83 17v78q0 9 83 18 7 16 17 29-29 63-29 77 0 2 3 4 2 1 19 11t33 19 17 9q4 0 25-26t29-38q12 1 17 1t17-1q28 40 51 63l4 1q2 0 69-39 2-2 2-4 0-14-28-77 9-13 16-29 83-9 83-18z m0 572v-78q0-9-83-18-6-15-16-29 28-63 28-77 0-2-2-4-68-39-69-39-5 0-26 26t-29 38q-11-1-17-1t-17 1q-7-12-29-38t-25-26q-1 0-69 39-3 2-3 4 0 14 29 77-10 14-17 29-83 9-83 18v78q0 9 83 17 7 16 17 29-29 63-29 77 0 2 3 4 2 1 19 11t33 19 17 9q4 0 25-26t29-37q12 1 17 1t17-1q28 39 51 62l4 1q2 0 69-39 2-2 2-4 0-14-28-77 9-13 16-29 83-8 83-17z" horiz-adv-x="1071.4" />
|
||||||
|
|
||||||
|
<glyph glyph-name="zoom-in" unicode="" d="M571 411v-36q0-7-5-13t-12-5h-125v-125q0-7-6-13t-12-5h-36q-7 0-13 5t-5 13v125h-125q-7 0-12 5t-6 13v36q0 7 6 12t12 5h125v125q0 8 5 13t13 5h36q7 0 12-5t6-13v-125h125q7 0 12-5t5-12z m72-18q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-21-50t-51-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
|
||||||
|
|
||||||
|
<glyph glyph-name="home-2" unicode="" d="M521 826q322-279 500-429 20-16 20-40 0-21-15-37t-36-15l-105 0 0-364q0-21-15-37t-36-16l-156 0q-22 0-37 16t-16 37l0 208-209 0 0-208q0-21-15-37t-36-16l-156 0q-21 0-37 16t-16 37l0 364-103 0q-22 0-37 15t-16 37 19 40z" horiz-adv-x="1041" />
|
||||||
|
|
||||||
|
<glyph glyph-name="spin3" unicode="" d="M494 857c-266 0-483-210-494-472-1-19 13-20 13-20l84 0c16 0 19 10 19 18 10 199 176 358 378 358 107 0 205-45 273-118l-58-57c-11-12-11-27 5-31l247-50c21-5 46 11 37 44l-58 227c-2 9-16 22-29 13l-65-60c-89 91-214 148-352 148z m409-508c-16 0-19-10-19-18-10-199-176-358-377-358-108 0-205 45-274 118l59 57c10 12 10 27-5 31l-248 50c-21 5-46-11-37-44l58-227c2-9 16-22 30-13l64 60c89-91 214-148 353-148 265 0 482 210 493 473 1 18-13 19-13 19l-84 0z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="spin4" unicode="" d="M498 857c-114 0-228-39-320-116l0 0c173 140 428 130 588-31 134-134 164-332 89-495-10-29-5-50 12-68 21-20 61-23 84 0 3 3 12 15 15 24 71 180 33 393-112 539-99 98-228 147-356 147z m-409-274c-14 0-29-5-39-16-3-3-13-15-15-24-71-180-34-393 112-539 185-185 479-195 676-31l0 0c-173-140-428-130-589 31-134 134-163 333-89 495 11 29 6 50-12 68-11 11-27 17-44 16z" horiz-adv-x="1001" />
|
||||||
|
|
||||||
|
<glyph glyph-name="link-ext" unicode="" d="M786 339v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-25t-25-11-25 11l-98 98-364-364q-5-6-13-6t-12 6l-64 64q-6 5-6 12t6 13l364 364-98 98q-11 11-11 25t11 25 25 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="link-ext-alt" unicode="" d="M714 339v268q0 15-10 25t-25 11h-268q-24 0-33-22-10-23 8-39l80-80-298-298q-11-11-11-26t11-25l57-57q11-10 25-10t25 10l298 298 81-80q10-11 25-11 6 0 14 3 21 10 21 33z m143 286v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="menu" unicode="" d="M857 107v-71q0-15-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 25t25 11h785q15 0 26-11t10-25z m0 286v-72q0-14-10-25t-26-10h-785q-15 0-25 10t-11 25v72q0 14 11 25t25 10h785q15 0 26-10t10-25z m0 285v-71q0-14-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 26t25 10h785q15 0 26-10t10-26z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="mail-alt" unicode="" d="M1000 461v-443q0-37-26-63t-63-27h-822q-36 0-63 27t-26 63v443q25-27 56-49 202-137 278-192 32-24 51-37t53-27 61-13h2q28 0 61 13t53 27 51 37q95 68 278 192 32 22 56 49z m0 164q0-44-27-84t-68-69q-210-146-262-181-5-4-23-17t-30-22-29-18-32-15-28-5h-2q-12 0-27 5t-32 15-30 18-30 22-23 17q-51 35-147 101t-114 80q-35 23-65 64t-31 77q0 43 23 72t66 29h822q36 0 63-26t26-63z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="gauge" unicode="" d="M214 214q0 30-21 51t-50 21-51-21-21-51 21-50 51-21 50 21 21 50z m107 250q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m239-268l57 213q3 14-5 27t-21 16-27-3-17-22l-56-213q-33-3-60-25t-35-55q-11-43 11-81t66-50 81 11 50 66q9 33-4 65t-40 51z m369 18q0 30-21 51t-51 21-50-21-21-51 21-50 50-21 51 21 21 50z m-358 357q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m250-107q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m179-250q0-145-79-269-10-17-30-17h-782q-20 0-30 17-79 123-79 269 0 102 40 194t106 160 160 107 194 39 194-39 160-107 106-160 40-194z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="comment-empty" unicode="" d="M500 643q-114 0-213-39t-157-105-59-142q0-62 40-119t113-98l48-28-15-53q-13-51-39-97 85 36 154 96l24 21 32-3q38-5 72-5 114 0 213 39t157 105 59 142-59 142-157 105-213 39z m500-286q0-97-67-179t-182-130-251-48q-39 0-81 4-110-97-257-135-27-8-63-12h-3q-8 0-15 6t-9 15v1q-2 2 0 6t1 6 2 5l4 5t4 5 4 5q4 5 17 19t20 22 17 22 18 28 15 33 15 42q-88 50-138 123t-51 157q0 97 67 179t182 130 251 48 251-48 182-130 67-179z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="bell-alt" unicode="" d="M509-89q0 8-9 8-33 0-57 24t-23 57q0 9-9 9t-9-9q0-41 29-70t69-28q9 0 9 9z m455 160q0-29-21-50t-50-21h-250q0-59-42-101t-101-42-101 42-42 101h-250q-29 0-50 21t-21 50q28 24 51 49t47 67 42 89 27 115 11 145q0 84 66 157t171 89q-5 10-5 21 0 23 16 38t38 16 38-16 16-38q0-11-5-21 106-16 171-89t66-157q0-78 11-145t28-115 41-89 48-67 50-49z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="plus-squared" unicode="" d="M714 321v72q0 14-10 25t-25 10h-179v179q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-179h-178q-15 0-25-10t-11-25v-72q0-14 11-25t25-10h178v-179q0-14 11-25t25-11h71q15 0 25 11t11 25v179h179q14 0 25 10t10 25z m143 304v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="laptop" unicode="" d="M232 143q-37 0-63 26t-26 63v393q0 37 26 63t63 26h607q37 0 63-26t27-63v-393q0-37-27-63t-63-26h-607z m-18 482v-393q0-7 6-13t12-5h607q8 0 13 5t5 13v393q0 7-5 12t-13 6h-607q-7 0-12-6t-6-12z m768-518h89v-54q0-22-26-37t-63-16h-893q-36 0-63 16t-26 37v54h982z m-402-54q9 0 9 9t-9 9h-89q-9 0-9-9t9-9h89z" horiz-adv-x="1071.4" />
|
||||||
|
|
||||||
|
<glyph glyph-name="quote-right" unicode="" d="M429 678v-392q0-58-23-111t-61-91-91-61-111-23h-36q-14 0-25 11t-11 25v71q0 15 11 25t25 11h36q59 0 101 42t42 101v17q0 23-16 38t-38 16h-125q-44 0-76 31t-31 76v214q0 45 31 76t76 32h214q45 0 76-32t32-76z m500 0v-392q0-58-23-111t-61-91-91-61-111-23h-36q-14 0-25 11t-11 25v71q0 15 11 25t25 11h36q59 0 101 42t42 101v17q0 23-16 38t-38 16h-125q-44 0-76 31t-31 76v214q0 45 31 76t76 32h214q45 0 76-32t32-76z" horiz-adv-x="928.6" />
|
||||||
|
|
||||||
|
<glyph glyph-name="reply" unicode="" d="M1000 232q0-93-71-252-1-4-6-13t-7-17-7-12q-7-10-16-10-8 0-13 6t-5 14q0 5 1 15t2 13q3 38 3 69 0 56-10 101t-27 77-45 56-59 39-74 24-86 12-98 3h-125v-143q0-14-10-25t-26-11-25 11l-285 286q-11 10-11 25t11 25l285 286q11 10 25 10t26-10 10-25v-143h125q398 0 488-225 30-75 30-186z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="smile" unicode="" d="M633 257q-21-67-77-109t-127-41-128 41-77 109q-4 14 3 27t21 18q14 4 27-2t17-22q14-44 52-72t85-28 84 28 52 72q4 15 18 22t27 2 21-18 2-27z m-276 243q0-30-21-51t-50-21-51 21-21 51 21 50 51 21 50-21 21-50z m286 0q0-30-21-51t-51-21-50 21-21 51 21 50 50 21 51-21 21-50z m143-143q0 73-29 139t-76 114-114 76-138 28-139-28-114-76-76-114-29-139 29-139 76-113 114-77 139-28 138 28 114 77 76 113 29 139z m71 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="lock-open-alt" unicode="" d="M589 428q23 0 38-15t16-38v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v179q0 103 74 177t176 73 177-73 73-177q0-14-10-25t-25-11h-36q-14 0-25 11t-11 25q0 59-42 101t-101 42-101-42-41-101v-179h410z" horiz-adv-x="642.9" />
|
||||||
|
|
||||||
|
<glyph glyph-name="ellipsis" unicode="" d="M214 446v-107q0-22-15-38t-38-15h-107q-23 0-38 15t-16 38v107q0 23 16 38t38 16h107q22 0 38-16t15-38z m286 0v-107q0-22-16-38t-38-15h-107q-22 0-38 15t-15 38v107q0 23 15 38t38 16h107q23 0 38-16t16-38z m286 0v-107q0-22-16-38t-38-15h-107q-22 0-38 15t-16 38v107q0 23 16 38t38 16h107q23 0 38-16t16-38z" horiz-adv-x="785.7" />
|
||||||
|
|
||||||
|
<glyph glyph-name="play-circled" unicode="" d="M429 786q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m214-460q18 10 18 31t-18 31l-304 178q-17 11-35 1-18-11-18-31v-358q0-20 18-31 9-4 17-4 10 0 18 5z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="thumbs-up-alt" unicode="" d="M143 107q0 15-11 25t-25 11q-15 0-25-11t-11-25q0-15 11-25t25-11q15 0 25 11t11 25z m89 286v-357q0-15-10-25t-26-11h-160q-15 0-25 11t-11 25v357q0 14 11 25t25 10h160q15 0 26-10t10-25z m661 0q0-48-31-83 9-25 9-43 1-42-24-76 9-31 0-66-9-31-31-52 5-62-27-101-36-43-110-44h-72q-37 0-80 9t-68 16-67 22q-69 24-88 25-15 0-25 11t-11 25v357q0 14 10 25t24 11q13 1 42 33t57 67q38 49 56 67 10 10 17 27t10 27 8 34q4 22 7 34t11 29 19 28q10 11 25 11 25 0 46-6t33-15 22-22 14-25 7-28 2-25 1-22q0-21-6-43t-10-33-16-31q-1-4-5-10t-6-13-5-13h155q43 0 75-32t32-75z" horiz-adv-x="928.6" />
|
||||||
|
|
||||||
|
<glyph glyph-name="apple" unicode="" d="M777 179q-21-70-68-139-72-110-144-110-27 0-78 18-48 18-84 18-34 0-79-19-45-19-74-19-85 0-168 145-82 146-82 281 0 127 63 208 63 81 159 81 40 0 98-17 58-17 77-17 25 0 80 19 57 19 97 19 66 0 119-36 29-20 58-56-44-37-64-66-36-52-36-115 0-69 38-125t88-70z m-209 655q0-34-17-76-16-42-52-77-30-30-60-40-20-7-58-10 2 83 44 143 41 60 139 83 1-2 2-6t1-6q0-2 0-6t1-5z" horiz-adv-x="785.7" />
|
||||||
|
|
||||||
|
<glyph glyph-name="android" unicode="" d="M275 588q9 0 16 6t6 15-6 16-16 6-15-6-6-16 6-15 15-6z m236 0q9 0 15 6t6 15-6 16-15 6-16-6-6-16 6-15 16-6z m-453-103q23 0 40-17t16-40v-240q0-24-16-41t-40-17-41 17-17 41v240q0 23 17 40t41 17z m591-11v-371q0-26-18-44t-43-18h-42v-127q0-24-16-40t-41-17-41 17-17 40v127h-77v-127q0-24-16-40t-41-17q-24 0-40 17t-17 40l-1 127h-41q-26 0-43 18t-18 44v371h512z m-129 226q59-30 95-85t36-121h-516q0 66 35 121t96 85l-39 73q-4 8 2 12 8 3 12-4l40-74q53 24 112 24t112-24l40 74q4 7 11 4 7-4 3-12z m266-272v-240q0-24-17-41t-41-17q-23 0-40 17t-17 41v240q0 24 17 40t40 17q24 0 41-17t17-40z" horiz-adv-x="785.7" />
|
||||||
|
|
||||||
|
<glyph glyph-name="paper-plane-empty" unicode="" d="M984 851q19-13 15-36l-142-857q-3-16-18-25-8-5-18-5-6 0-13 3l-294 120-166-182q-10-12-27-12-7 0-12 2-11 4-17 13t-6 21v252l-264 108q-20 8-22 30-2 22 18 33l928 536q20 12 38-1z m-190-837l123 739-800-462 187-76 482 356-267-444z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="binoculars" unicode="" d="M393 678v-428q0-15-11-25t-25-11v-321q0-15-10-25t-26-11h-285q-15 0-25 11t-11 25v285l139 488q4 12 17 12h237z m178 0v-392h-142v392h142z m429-500v-285q0-15-11-25t-25-11h-285q-15 0-25 11t-11 25v321q-15 0-25 11t-11 25v428h237q13 0 17-12z m-589 661v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z m375 0v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="user-plus" unicode="" d="M393 357q-89 0-152 63t-62 151 62 152 152 63 151-63 63-152-63-151-151-63z m536-71h196q7 0 13-6t5-12v-107q0-8-5-13t-13-5h-196v-197q0-7-6-12t-12-6h-107q-8 0-13 6t-5 12v197h-197q-7 0-12 5t-6 13v107q0 7 6 12t12 6h197v196q0 7 5 13t13 5h107q7 0 12-5t6-13v-196z m-411-125q0-29 21-51t50-21h143v-133q-38-28-95-28h-488q-67 0-108 39t-41 106q0 30 2 58t8 61 15 60 24 55 34 45 48 30 62 11q11 0 22-10 44-34 86-51t92-17 92 17 86 51q11 10 22 10 73 0 121-54h-125q-29 0-50-21t-21-50v-107z" horiz-adv-x="1142.9" />
|
||||||
|
|
||||||
|
<glyph glyph-name="hashtag" unicode="" d="M553 286l36 142h-142l-36-142h142z m429 281l-32-125q-4-14-17-14h-182l-36-142h173q9 0 14-7 6-8 4-16l-32-125q-2-13-17-13h-182l-45-183q-4-14-18-14h-125q-9 0-14 7-5 7-4 16l44 174h-142l-45-183q-4-14-17-14h-126q-8 0-14 7-5 7-3 16l43 174h-173q-9 0-14 7-5 6-4 15l32 125q4 14 17 14h182l36 142h-173q-9 0-14 7-6 8-4 16l32 125q2 13 17 13h182l46 183q3 14 17 14h125q9 0 14-7 5-7 4-16l-44-174h142l45 183q4 14 18 14h125q8 0 14-7 5-7 3-16l-43-174h173q9 0 14-7 5-6 4-15z" horiz-adv-x="1000" />
|
||||||
|
</font>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 28 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,126 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<metadata>Copyright (C) 2019 by original authors @ fontello.com</metadata>
|
||||||
|
<defs>
|
||||||
|
<font id="fontello" horiz-adv-x="1000" >
|
||||||
|
<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="857" descent="-143" />
|
||||||
|
<missing-glyph horiz-adv-x="1000" />
|
||||||
|
<glyph glyph-name="cancel" unicode="" d="M724 119q0-22-15-38l-76-76q-16-15-38-15t-38 15l-164 165-164-165q-16-15-38-15t-38 15l-76 76q-16 16-16 38t16 38l164 164-164 164q-16 16-16 38t16 38l76 76q16 16 38 16t38-16l164-164 164 164q16 16 38 16t38-16l76-76q15-15 15-38t-15-38l-164-164 164-164q15-15 15-38z" horiz-adv-x="785.7" />
|
||||||
|
|
||||||
|
<glyph glyph-name="upload" unicode="" d="M714 36q0 14-10 25t-25 10-25-10-11-25 11-25 25-11 25 11 10 25z m143 0q0 14-10 25t-26 10-25-10-10-25 10-25 25-11 26 11 10 25z m72 125v-179q0-22-16-38t-38-16h-821q-23 0-38 16t-16 38v179q0 22 16 38t38 15h238q12-31 39-51t62-20h143q34 0 61 20t40 51h238q22 0 38-15t16-38z m-182 361q-9-22-33-22h-143v-250q0-15-10-25t-25-11h-143q-15 0-25 11t-11 25v250h-143q-23 0-33 22-9 22 8 39l250 250q10 10 25 10t25-10l250-250q18-17 8-39z" horiz-adv-x="928.6" />
|
||||||
|
|
||||||
|
<glyph glyph-name="star" unicode="" d="M929 496q0-12-15-27l-202-197 48-279q0-4 0-12 0-11-6-19t-17-9q-10 0-22 7l-251 132-250-132q-12-7-23-7-11 0-17 9t-6 19q0 4 1 12l48 279-203 197q-14 15-14 27 0 21 31 26l280 40 126 254q11 23 27 23t28-23l125-254 280-40q32-5 32-26z" horiz-adv-x="928.6" />
|
||||||
|
|
||||||
|
<glyph glyph-name="star-empty" unicode="" d="M635 297l170 166-235 34-106 213-105-213-236-34 171-166-41-235 211 111 211-111z m294 199q0-12-15-27l-202-197 48-279q0-4 0-12 0-28-23-28-10 0-22 7l-251 132-250-132q-12-7-23-7-11 0-17 9t-6 19q0 4 1 12l48 279-203 197q-14 15-14 27 0 21 31 26l280 40 126 254q11 23 27 23t28-23l125-254 280-40q32-5 32-26z" horiz-adv-x="928.6" />
|
||||||
|
|
||||||
|
<glyph glyph-name="retweet" unicode="" d="M714 18q0-7-5-13t-13-5h-535q-5 0-8 1t-5 4-3 4-2 7 0 6v335h-107q-15 0-25 11t-11 25q0 13 8 23l179 214q11 12 27 12t28-12l178-214q9-10 9-23 0-15-11-25t-25-11h-107v-214h321q9 0 14-6l89-108q4-5 4-11z m357 232q0-13-8-23l-178-214q-12-13-28-13t-27 13l-179 214q-8 10-8 23 0 14 11 25t25 11h107v214h-322q-9 0-14 7l-89 107q-4 5-4 11 0 7 5 12t13 6h536q4 0 7-1t5-4 3-5 2-6 1-7v-334h107q14 0 25-11t10-25z" horiz-adv-x="1071.4" />
|
||||||
|
|
||||||
|
<glyph glyph-name="eye-off" unicode="" d="M310 112l43 79q-48 35-76 88t-27 114q0 67 34 125-128-65-213-197 94-144 239-209z m217 424q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-11 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m202 106q0-4 0-5-59-105-176-316t-176-316l-28-50q-5-9-15-9-7 0-75 39-9 6-9 16 0 7 25 49-80 36-147 96t-117 137q-11 17-11 38t11 39q86 131 212 207t277 76q50 0 100-10l31 54q5 9 15 9 3 0 10-3t18-9 18-10 18-10 10-7q9-5 9-15z m21-249q0-78-44-142t-117-91l157 280q4-25 4-47z m250-72q0-19-11-38-22-36-61-81-84-96-194-149t-234-53l41 74q119 10 219 76t169 171q-65 100-158 164l35 63q53-36 102-85t81-103q11-19 11-39z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="search" unicode="" d="M643 393q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-22-50t-50-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
|
||||||
|
|
||||||
|
<glyph glyph-name="cog" unicode="" d="M571 357q0 59-41 101t-101 42-101-42-42-101 42-101 101-42 101 42 41 101z m286 61v-124q0-7-4-13t-11-7l-104-16q-10-30-21-51 19-27 59-77 6-6 6-13t-5-13q-15-21-55-61t-53-39q-7 0-14 5l-77 60q-25-13-51-21-9-76-16-104-4-16-20-16h-124q-8 0-14 5t-6 12l-16 103q-27 9-50 21l-79-60q-6-5-14-5-8 0-14 6-70 64-92 94-4 5-4 13 0 6 5 12 8 12 28 37t30 40q-15 28-23 55l-102 15q-7 1-11 7t-5 13v124q0 7 5 13t10 7l104 16q8 25 22 51-23 32-60 77-6 7-6 14 0 5 5 12 15 20 55 60t53 40q7 0 15-5l77-60q24 13 50 21 9 76 17 104 3 16 20 16h124q7 0 13-5t7-12l15-103q28-9 51-20l79 59q5 5 13 5 7 0 14-5 72-67 92-95 4-5 4-12 0-7-4-13-9-12-29-37t-30-40q15-28 23-54l102-16q7-1 12-7t4-13z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="logout" unicode="" d="M357 53q0-2 1-11t0-14-2-14-5-10-12-4h-178q-67 0-114 47t-47 114v392q0 67 47 114t114 47h178q8 0 13-5t5-13q0-2 1-11t0-15-2-13-5-11-12-3h-178q-37 0-63-26t-27-64v-392q0-37 27-63t63-27h174t6 0 7-2 4-3 4-5 1-8z m518 304q0-14-11-25l-303-304q-11-10-25-10t-25 10-11 25v161h-250q-14 0-25 11t-11 25v214q0 15 11 25t25 11h250v161q0 14 11 25t25 10 25-10l303-304q11-10 11-25z" horiz-adv-x="928.6" />
|
||||||
|
|
||||||
|
<glyph glyph-name="down-open" unicode="" d="M939 406l-414-413q-10-11-25-11t-25 11l-414 413q-11 11-11 26t11 25l93 92q10 11 25 11t25-11l296-296 296 296q11 11 25 11t26-11l92-92q11-11 11-25t-11-26z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="attach" unicode="" d="M244-133q-102 0-170 72-72 70-74 166t84 190l496 496q80 80 174 54 44-12 79-47t47-79q26-96-54-176l-474-474q-40-40-88-46-48-4-80 28-30 24-27 74t47 92l332 334q24 26 50 0t0-50l-332-332q-44-44-20-70 12-8 24-6 24 4 46 26l474 474q50 50 34 108-16 60-76 76-54 14-108-36l-494-494q-66-76-64-143t52-117q50-48 117-50t141 62l496 494q24 24 50 0 26-22 0-48l-496-496q-82-82-186-82z" horiz-adv-x="939" />
|
||||||
|
|
||||||
|
<glyph glyph-name="picture" unicode="" d="M357 536q0-45-31-76t-76-32-76 32-31 76 31 76 76 31 76-31 31-76z m572-215v-250h-786v107l178 179 90-89 285 285z m53 393h-893q-7 0-12-5t-6-13v-678q0-7 6-13t12-5h893q7 0 13 5t5 13v678q0 8-5 13t-13 5z m89-18v-678q0-37-26-63t-63-27h-893q-36 0-63 27t-26 63v678q0 37 26 63t63 27h893q37 0 63-27t26-63z" horiz-adv-x="1071.4" />
|
||||||
|
|
||||||
|
<glyph glyph-name="video" unicode="" d="M214-36v72q0 14-10 25t-25 10h-72q-14 0-25-10t-11-25v-72q0-14 11-25t25-11h72q14 0 25 11t10 25z m0 214v72q0 14-10 25t-25 11h-72q-14 0-25-11t-11-25v-72q0-14 11-25t25-10h72q14 0 25 10t10 25z m0 215v71q0 15-10 25t-25 11h-72q-14 0-25-11t-11-25v-71q0-15 11-25t25-11h72q14 0 25 11t10 25z m572-429v286q0 14-11 25t-25 11h-429q-14 0-25-11t-10-25v-286q0-14 10-25t25-11h429q15 0 25 11t11 25z m-572 643v71q0 15-10 26t-25 10h-72q-14 0-25-10t-11-26v-71q0-14 11-25t25-11h72q14 0 25 11t10 25z m786-643v72q0 14-11 25t-25 10h-71q-15 0-25-10t-11-25v-72q0-14 11-25t25-11h71q15 0 25 11t11 25z m-214 429v285q0 15-11 26t-25 10h-429q-14 0-25-10t-10-26v-285q0-15 10-25t25-11h429q15 0 25 11t11 25z m214-215v72q0 14-11 25t-25 11h-71q-15 0-25-11t-11-25v-72q0-14 11-25t25-10h71q15 0 25 10t11 25z m0 215v71q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-71q0-15 11-25t25-11h71q15 0 25 11t11 25z m0 214v71q0 15-11 26t-25 10h-71q-15 0-25-10t-11-26v-71q0-14 11-25t25-11h71q15 0 25 11t11 25z m71 89v-750q0-37-26-63t-63-26h-893q-36 0-63 26t-26 63v750q0 37 26 63t63 27h893q37 0 63-27t26-63z" horiz-adv-x="1071.4" />
|
||||||
|
|
||||||
|
<glyph glyph-name="right-open" unicode="" d="M618 368l-414-415q-11-10-25-10t-25 10l-93 93q-11 11-11 25t11 25l296 297-296 296q-11 11-11 25t11 25l93 93q10 11 25 11t25-11l414-414q10-11 10-25t-10-25z" horiz-adv-x="714.3" />
|
||||||
|
|
||||||
|
<glyph glyph-name="left-open" unicode="" d="M654 689l-297-296 297-297q10-10 10-25t-10-25l-93-93q-11-10-25-10t-25 10l-414 415q-11 10-11 25t11 25l414 414q10 11 25 11t25-11l93-93q10-10 10-25t-10-25z" horiz-adv-x="714.3" />
|
||||||
|
|
||||||
|
<glyph glyph-name="up-open" unicode="" d="M939 114l-92-92q-11-10-26-10t-25 10l-296 297-296-297q-11-10-25-10t-25 10l-93 92q-11 11-11 26t11 25l414 414q11 10 25 10t25-10l414-414q11-11 11-25t-11-26z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="bell-ringing-o" unicode="" d="M498 857c-30 0-54-24-54-53 0-8 2-15 5-22-147-22-236-138-236-245 0-268-95-393-177-462 0-39 32-71 71-71h249c0-79 63-143 142-143s142 64 142 143h249c39 0 71 32 71 71-82 69-178 194-178 462 0 107-88 223-235 245 2 7 4 14 4 22 0 29-24 53-53 53z m-309-45c-81-74-118-170-118-275l71 0c0 89 28 162 95 223l-48 52z m617 0l-48-52c67-61 96-134 95-223l71 0c1 105-37 201-118 275z m-397-799c5 0 9-4 9-9 0-44 36-80 80-80 5 0 9-4 9-9s-4-9-9-9c-54 0-98 44-98 98 0 5 4 9 9 9z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="lock" unicode="" d="M179 428h285v108q0 59-42 101t-101 41-101-41-41-101v-108z m464-53v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v108q0 102 74 176t176 74 177-74 73-176v-108h18q23 0 38-15t16-38z" horiz-adv-x="642.9" />
|
||||||
|
|
||||||
|
<glyph glyph-name="globe" unicode="" d="M429 786q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m153-291q-2-1-6-5t-7-6q1 0 2 3t3 6 2 4q3 4 12 8 8 4 29 7 19 5 29-6-1 1 5 7t8 7q2 1 8 3t9 4l1 12q-7-1-10 4t-3 12q0-2-4-5 0 4-2 5t-7-1-5-1q-5 2-8 5t-5 9-2 8q-1 3-5 6t-5 6q-1 1-2 3t-1 4-3 3-3 1-4-3-4-5-2-3q-2 1-4 1t-2-1-3-1-3-2q-1-2-4-2t-5-1q8 3-1 6-5 2-9 2 6 2 5 6t-5 8h3q-1 2-5 5t-10 5-7 3q-5 3-19 5t-18 1q-3-4-3-6t2-8 2-7q1-3-3-7t-3-7q0-4 7-9t6-12q-2-4-9-9t-9-6q-3-5-1-11t6-9q1-1 1-2t-2-3-3-2-4-2l-1-1q-7-3-12 3t-7 15q-4 14-9 17-13 4-16-1-3 7-23 15-14 5-33 2 4 0 0 8-4 9-10 7 1 3 2 10t0 7q2 8 7 13 1 1 4 5t5 7 1 4q19-3 28 6 2 3 6 9t6 10q5 3 8 3t8-3 8-3q8-1 8 6t-4 11q7 0 2 10-2 4-5 5-6 2-15-3-4-2 2-4-1 0-6-6t-9-10-9 3q0 0-3 7t-5 8q-5 0-9-9 1 5-6 9t-14 4q11 7-4 15-4 3-12 3t-11-2q-2-4-3-7t3-4 6-3 6-2 5-2q8-6 5-8-1 0-5-2t-6-2-4-2q-1-3 0-8t-1-8q-3 3-5 10t-4 9q4-5-14-3l-5 0q-3 0-9-1t-12-1-7 5q-3 4 0 11 0 2 2 1-2 2-6 5t-6 5q-25-8-52-23 3 0 6 1 3 1 8 4t5 3q19 7 24 4l3 2q7-9 11-14-4 3-17 1-11-3-12-7 4-6 2-10-2 2-6 6t-8 6-8 3q-9 0-13-1-81-45-131-124 4-4 7-4 2-1 3-5t1-6 6 1q5-4 2-10 1 0 25-15 10-10 11-12 2-6-5-10-1 1-5 5t-5 2q-2-3 0-10t6-7q-4 0-5-9t-2-20 0-13l1-1q-2-6 3-19t12-11q-7-1 11-24 3-4 4-5 2-1 7-4t9-6 5-5q2-3 6-13t8-13q-2-3 5-11t6-13q-1 0-2-1t-1 0q2-4 9-8t8-7q1-2 1-6t2-6 4-1q2 11-13 35-8 13-9 16-2 2-4 8t-2 8q1 0 3 0t5-2 4-3 1-1q-1-4 1-10t7-10 10-11 6-7q4-4 8-11t0-8q5 0 11-5t10-11q3-5 4-15t3-13q1-4 5-8t7-5l9-5t7-3q3-2 10-6t12-7q6-2 9-2t8 1 8 2q8 1 16-8t12-12q20-10 30-6-1 0 1-4t4-9 5-8 3-5q3-3 10-8t10-8q4 2 4 5-1-5 4-11t10-6q8 2 8 18-17-8-27 10 0 0-2 3t-2 5-1 4 0 5 2 1q5 0 6 2t-1 7-2 8q-1 4-6 11t-7 8q-3-5-9-4t-9 5q0-1-1-3t-1-4q-7 0-8 0 1 2 1 10t2 13q1 2 3 6t5 9 2 7-3 5-9 1q-11 0-15-11-1-2-2-6t-2-6-5-4q-4-2-14-1t-13 3q-8 4-13 16t-5 20q0 6 1 15t2 14-3 14q2 1 5 5t5 6q2 1 3 1t3 0 2 1 1 3q0 1-2 2-1 1-2 1 4-1 16 1t15-1q9-6 12 1 0 1-1 6t0 7q3-15 16-5 2-1 9-3t9-2q2-1 4-3t3-3 3 0 5 4q5-8 7-13 6-23 10-25 4-2 6-1t3 5 0 8-1 7l-1 5v10l0 4q-8 2-10 7t0 10 9 10q0 1 4 2t9 4 7 4q12 11 8 20 4 0 6 5 0 0-2 2t-5 2-2 2q5 2 1 8 3 2 4 7t4 5q5-6 12-1 5 5 1 9 2 4 11 6t10 5q4-1 5 1t0 7 2 7q2 2 9 5t7 2l9 7q2 2 0 2 10-1 18 6 5 6-4 11 2 4-1 5t-9 4q2 0 7 0t5 1q9 5-3 9-10 2-24-7z m-91-490q115 21 195 106-1 2-7 2t-7 2q-10 4-13 5 1 4-1 7t-5 5-7 5-6 4q-1 1-4 3t-4 3-4 2-5 2-5-1l-2-1q-2 0-3-1t-3-2-2-1 0-2q-12 10-20 13-3 0-6 3t-6 4-6 0-6-3q-3-3-4-9t-1-7q-4 3 0 10t1 10q-1 3-6 2t-6-2-7-5-5-3-4-3-5-5q-2-2-4-6t-2-6q-1 2-7 3t-5 3q1-5 2-19t3-22q4-17-7-26-15-14-16-23-2-12 7-14 0-4-5-12t-4-12q0-3 2-9z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="brush" unicode="" d="M464 209q0-124-87-212t-210-87q-81 0-149 40 68 39 109 108t40 151q0 61 44 105t105 44 105-44 43-105z m415 562q32-32 32-79t-33-79l-318-318q-20 55-61 97t-97 62l318 318q32 32 79 32t80-33z" horiz-adv-x="928" />
|
||||||
|
|
||||||
|
<glyph glyph-name="attention" unicode="" d="M571 90v106q0 8-5 13t-12 5h-108q-7 0-12-5t-5-13v-106q0-8 5-13t12-6h108q7 0 12 6t5 13z m-1 208l10 257q0 6-5 10-7 6-14 6h-122q-6 0-14-6-5-4-5-12l9-255q0-5 6-9t13-3h103q8 0 14 3t5 9z m-7 522l428-786q20-35-1-70-9-17-26-26t-35-10h-858q-18 0-35 10t-26 26q-21 35-1 70l429 786q9 17 26 27t36 10 36-10 27-27z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="plus" unicode="" d="M786 446v-107q0-22-16-38t-38-15h-232v-233q0-22-16-37t-38-16h-107q-22 0-38 16t-15 37v233h-232q-23 0-38 15t-16 38v107q0 23 16 38t38 16h232v232q0 22 15 38t38 16h107q23 0 38-16t16-38v-232h232q23 0 38-16t16-38z" horiz-adv-x="785.7" />
|
||||||
|
|
||||||
|
<glyph glyph-name="adjust" unicode="" d="M429 53v608q-83 0-153-41t-110-111-41-152 41-152 110-111 153-41z m428 304q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="edit" unicode="" d="M496 196l64 65-85 85-64-65v-31h53v-54h32z m245 402q-9 9-18 0l-196-196q-9-9 0-18t18 0l196 196q9 9 0 18z m45-331v-106q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h464q35 0 65-14 9-4 10-13 2-10-5-16l-27-28q-8-8-18-4-13 3-25 3h-464q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v70q0 7 5 12l36 36q8 8 20 4t11-16z m-54 411l161-160-375-375h-161v160z m248-73l-51-52-161 161 51 52q16 15 38 15t38-15l85-85q16-16 16-38t-16-38z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="pencil" unicode="" d="M203 0l50 51-131 131-51-51v-60h72v-71h60z m291 518q0 12-12 12-5 0-9-4l-303-302q-4-4-4-10 0-12 13-12 5 0 9 4l303 302q3 4 3 10z m-30 107l232-232-464-465h-232v233z m381-54q0-29-20-50l-93-93-232 233 93 92q20 21 50 21 29 0 51-21l131-131q20-22 20-51z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="pin" unicode="" d="M268 375v250q0 8-5 13t-13 5-13-5-5-13v-250q0-8 5-13t13-5 13 5 5 13z m375-197q0-14-11-25t-25-10h-239l-29-270q-1-7-6-11t-11-5h-1q-15 0-17 15l-43 271h-225q-15 0-25 10t-11 25q0 69 44 124t99 55v286q-29 0-50 21t-22 50 22 50 50 22h357q29 0 50-22t21-50-21-50-50-21v-286q55 0 99-55t44-124z" horiz-adv-x="642.9" />
|
||||||
|
|
||||||
|
<glyph glyph-name="wrench" unicode="" d="M214 36q0 14-10 25t-25 10-25-10-11-25 11-25 25-11 25 11 10 25z m360 234l-381-381q-21-20-50-20-29 0-51 20l-59 61q-21 20-21 50 0 29 21 51l380 380q22-55 64-97t97-64z m354 243q0-22-13-59-27-75-92-122t-144-46q-104 0-177 73t-73 177 73 176 177 74q32 0 67-10t60-26q9-6 9-15t-9-16l-163-94v-125l108-60q2 2 44 27t75 45 40 20q8 0 13-5t5-14z" horiz-adv-x="928.6" />
|
||||||
|
|
||||||
|
<glyph glyph-name="verified" unicode="" d="M926 453l-19 13c-21 14-30 41-23 65l6 22c10 34-13 69-48 75l-23 4c-25 4-45 23-49 48l-4 23c-6 35-41 57-75 47l-22-7c-24-7-51 2-65 22l-14 20c-21 29-62 33-88 9l-17-16c-19-17-46-21-69-8l-20 11c-31 17-70 3-84-30l-9-22c-9-24-33-39-58-37l-23 1c-36 2-65-28-62-63l2-23c2-25-13-49-36-59l-21-9c-33-14-46-53-29-84l12-20c13-22 10-50-7-69l-15-17c-24-27-19-68 11-88l19-13c21-14 30-41 23-65l-9-23c-10-34 13-69 48-75l23-4c25-4 45-23 49-48l4-23c6-35 41-57 75-47l22 7c24 7 51-2 65-22l14-19c21-29 62-33 88-9l17 16c19 17 46 21 69 8l20-11c31-17 70-3 84 30l9 22c9 24 33 39 58 37l23-1c36-2 65 28 62 63l-1 23c-2 25 13 49 36 59l21 9c33 14 46 53 29 84l-12 20c-13 22-10 50 7 69l15 17c25 26 20 68-9 88z m-399-189l-82-81-81 82-78 79 82 81 78-79 187 186 81-82-187-186z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="chart-bar" unicode="" d="M357 357v-286h-143v286h143z m214 286v-572h-142v572h142z m572-643v-72h-1143v858h71v-786h1072z m-357 500v-429h-143v429h143z m214 214v-643h-143v643h143z" horiz-adv-x="1142.9" />
|
||||||
|
|
||||||
|
<glyph glyph-name="users" unicode="" d="M331 357q-90-3-148-71h-75q-45 0-77 22t-31 66q0 197 69 197 4 0 25-11t54-24 66-12q38 0 75 13-3-21-3-37 0-78 45-143z m598-355q0-67-41-106t-108-39h-488q-68 0-108 39t-41 106q0 29 2 57t8 61 14 61 24 54 35 45 48 30 62 11q6 0 24-12t41-26 59-27 76-12 75 12 60 27 41 26 24 12q34 0 62-11t47-30 35-45 24-54 15-61 8-61 2-57z m-572 712q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z m393-214q0-89-63-152t-151-62-152 62-63 152 63 151 152 63 151-63 63-151z m321-126q0-43-31-66t-77-22h-75q-57 68-147 71 45 65 45 143 0 16-3 37 37-13 74-13 33 0 67 12t54 24 24 11q69 0 69-197z m-71 340q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z" horiz-adv-x="1071.4" />
|
||||||
|
|
||||||
|
<glyph glyph-name="address-book" unicode="" d="M0-143l0 1000 1000 0 0-178-109 0 0-95 109 0 0-178-109 0 0-98 109 0 0-177-109 0 0-96 109 0 0-178-1000 0z m193 285l504 0 0 155-187 111q37 19 58 54t22 77q0 58-42 101t-103 43-102-43-42-101q0-43 22-77t58-54l-188-111 0-155z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="cog-alt" unicode="" d="M500 357q0 59-42 101t-101 42-101-42-42-101 42-101 101-42 101 42 42 101z m429-286q0 29-22 51t-50 21-50-21-21-51q0-29 21-50t50-21 51 21 21 50z m0 572q0 29-22 50t-50 21-50-21-21-50q0-30 21-51t50-21 51 21 21 51z m-215-235v-103q0-6-4-11t-8-6l-87-14q-6-19-18-42 19-27 50-64 4-6 4-11 0-7-4-11-12-17-46-50t-43-33q-7 0-12 4l-64 50q-21-11-43-17-6-60-13-87-4-13-17-13h-104q-6 0-11 4t-5 10l-13 85q-19 6-42 18l-66-50q-4-4-11-4-6 0-12 4-80 75-80 90 0 5 4 10 5 8 23 30t26 34q-13 24-20 46l-85 13q-5 1-9 5t-4 11v104q0 5 4 10t9 6l86 14q7 19 18 42-19 27-50 64-4 6-4 11 0 7 4 12 12 16 46 49t44 33q6 0 12-4l64-50q19 10 43 18 6 60 13 86 3 13 16 13h104q6 0 11-4t6-10l13-85q19-6 42-17l65 49q5 4 12 4 6 0 11-4 81-75 81-90 0-4-4-10-7-9-24-30t-25-34q13-27 19-46l85-12q6-2 9-6t4-11z m357-298v-78q0-9-83-17-6-15-16-29 28-63 28-77 0-2-2-4-68-40-69-40-5 0-26 27t-29 37q-11-1-17-1t-17 1q-7-11-29-37t-25-27q-1 0-69 40-3 2-3 4 0 14 29 77-10 14-17 29-83 8-83 17v78q0 9 83 18 7 16 17 29-29 63-29 77 0 2 3 4 2 1 19 11t33 19 17 9q4 0 25-26t29-38q12 1 17 1t17-1q28 40 51 63l4 1q2 0 69-39 2-2 2-4 0-14-28-77 9-13 16-29 83-9 83-18z m0 572v-78q0-9-83-18-6-15-16-29 28-63 28-77 0-2-2-4-68-39-69-39-5 0-26 26t-29 38q-11-1-17-1t-17 1q-7-12-29-38t-25-26q-1 0-69 39-3 2-3 4 0 14 29 77-10 14-17 29-83 9-83 18v78q0 9 83 17 7 16 17 29-29 63-29 77 0 2 3 4 2 1 19 11t33 19 17 9q4 0 25-26t29-37q12 1 17 1t17-1q28 39 51 62l4 1q2 0 69-39 2-2 2-4 0-14-28-77 9-13 16-29 83-8 83-17z" horiz-adv-x="1071.4" />
|
||||||
|
|
||||||
|
<glyph glyph-name="zoom-in" unicode="" d="M571 411v-36q0-7-5-13t-12-5h-125v-125q0-7-6-13t-12-5h-36q-7 0-13 5t-5 13v125h-125q-7 0-12 5t-6 13v36q0 7 6 12t12 5h125v125q0 8 5 13t13 5h36q7 0 12-5t6-13v-125h125q7 0 12-5t5-12z m72-18q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-21-50t-51-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
|
||||||
|
|
||||||
|
<glyph glyph-name="home-2" unicode="" d="M521 826q322-279 500-429 20-16 20-40 0-21-15-37t-36-15l-105 0 0-364q0-21-15-37t-36-16l-156 0q-22 0-37 16t-16 37l0 208-209 0 0-208q0-21-15-37t-36-16l-156 0q-21 0-37 16t-16 37l0 364-103 0q-22 0-37 15t-16 37 19 40z" horiz-adv-x="1041" />
|
||||||
|
|
||||||
|
<glyph glyph-name="spin3" unicode="" d="M494 857c-266 0-483-210-494-472-1-19 13-20 13-20l84 0c16 0 19 10 19 18 10 199 176 358 378 358 107 0 205-45 273-118l-58-57c-11-12-11-27 5-31l247-50c21-5 46 11 37 44l-58 227c-2 9-16 22-29 13l-65-60c-89 91-214 148-352 148z m409-508c-16 0-19-10-19-18-10-199-176-358-377-358-108 0-205 45-274 118l59 57c10 12 10 27-5 31l-248 50c-21 5-46-11-37-44l58-227c2-9 16-22 30-13l64 60c89-91 214-148 353-148 265 0 482 210 493 473 1 18-13 19-13 19l-84 0z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="spin4" unicode="" d="M498 857c-114 0-228-39-320-116l0 0c173 140 428 130 588-31 134-134 164-332 89-495-10-29-5-50 12-68 21-20 61-23 84 0 3 3 12 15 15 24 71 180 33 393-112 539-99 98-228 147-356 147z m-409-274c-14 0-29-5-39-16-3-3-13-15-15-24-71-180-34-393 112-539 185-185 479-195 676-31l0 0c-173-140-428-130-589 31-134 134-163 333-89 495 11 29 6 50-12 68-11 11-27 17-44 16z" horiz-adv-x="1001" />
|
||||||
|
|
||||||
|
<glyph glyph-name="link-ext" unicode="" d="M786 339v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-25t-25-11-25 11l-98 98-364-364q-5-6-13-6t-12 6l-64 64q-6 5-6 12t6 13l364 364-98 98q-11 11-11 25t11 25 25 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="link-ext-alt" unicode="" d="M714 339v268q0 15-10 25t-25 11h-268q-24 0-33-22-10-23 8-39l80-80-298-298q-11-11-11-26t11-25l57-57q11-10 25-10t25 10l298 298 81-80q10-11 25-11 6 0 14 3 21 10 21 33z m143 286v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="menu" unicode="" d="M857 107v-71q0-15-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 25t25 11h785q15 0 26-11t10-25z m0 286v-72q0-14-10-25t-26-10h-785q-15 0-25 10t-11 25v72q0 14 11 25t25 10h785q15 0 26-10t10-25z m0 285v-71q0-14-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 26t25 10h785q15 0 26-10t10-26z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="mail-alt" unicode="" d="M1000 461v-443q0-37-26-63t-63-27h-822q-36 0-63 27t-26 63v443q25-27 56-49 202-137 278-192 32-24 51-37t53-27 61-13h2q28 0 61 13t53 27 51 37q95 68 278 192 32 22 56 49z m0 164q0-44-27-84t-68-69q-210-146-262-181-5-4-23-17t-30-22-29-18-32-15-28-5h-2q-12 0-27 5t-32 15-30 18-30 22-23 17q-51 35-147 101t-114 80q-35 23-65 64t-31 77q0 43 23 72t66 29h822q36 0 63-26t26-63z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="gauge" unicode="" d="M214 214q0 30-21 51t-50 21-51-21-21-51 21-50 51-21 50 21 21 50z m107 250q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m239-268l57 213q3 14-5 27t-21 16-27-3-17-22l-56-213q-33-3-60-25t-35-55q-11-43 11-81t66-50 81 11 50 66q9 33-4 65t-40 51z m369 18q0 30-21 51t-51 21-50-21-21-51 21-50 50-21 51 21 21 50z m-358 357q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m250-107q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m179-250q0-145-79-269-10-17-30-17h-782q-20 0-30 17-79 123-79 269 0 102 40 194t106 160 160 107 194 39 194-39 160-107 106-160 40-194z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="comment-empty" unicode="" d="M500 643q-114 0-213-39t-157-105-59-142q0-62 40-119t113-98l48-28-15-53q-13-51-39-97 85 36 154 96l24 21 32-3q38-5 72-5 114 0 213 39t157 105 59 142-59 142-157 105-213 39z m500-286q0-97-67-179t-182-130-251-48q-39 0-81 4-110-97-257-135-27-8-63-12h-3q-8 0-15 6t-9 15v1q-2 2 0 6t1 6 2 5l4 5t4 5 4 5q4 5 17 19t20 22 17 22 18 28 15 33 15 42q-88 50-138 123t-51 157q0 97 67 179t182 130 251 48 251-48 182-130 67-179z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="bell-alt" unicode="" d="M509-89q0 8-9 8-33 0-57 24t-23 57q0 9-9 9t-9-9q0-41 29-70t69-28q9 0 9 9z m455 160q0-29-21-50t-50-21h-250q0-59-42-101t-101-42-101 42-42 101h-250q-29 0-50 21t-21 50q28 24 51 49t47 67 42 89 27 115 11 145q0 84 66 157t171 89q-5 10-5 21 0 23 16 38t38 16 38-16 16-38q0-11-5-21 106-16 171-89t66-157q0-78 11-145t28-115 41-89 48-67 50-49z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="plus-squared" unicode="" d="M714 321v72q0 14-10 25t-25 10h-179v179q0 15-11 25t-25 11h-71q-15 0-25-11t-11-25v-179h-178q-15 0-25-10t-11-25v-72q0-14 11-25t25-10h178v-179q0-14 11-25t25-11h71q15 0 25 11t11 25v179h179q14 0 25 10t10 25z m143 304v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="laptop" unicode="" d="M232 143q-37 0-63 26t-26 63v393q0 37 26 63t63 26h607q37 0 63-26t27-63v-393q0-37-27-63t-63-26h-607z m-18 482v-393q0-7 6-13t12-5h607q8 0 13 5t5 13v393q0 7-5 12t-13 6h-607q-7 0-12-6t-6-12z m768-518h89v-54q0-22-26-37t-63-16h-893q-36 0-63 16t-26 37v54h982z m-402-54q9 0 9 9t-9 9h-89q-9 0-9-9t9-9h89z" horiz-adv-x="1071.4" />
|
||||||
|
|
||||||
|
<glyph glyph-name="quote-right" unicode="" d="M429 678v-392q0-58-23-111t-61-91-91-61-111-23h-36q-14 0-25 11t-11 25v71q0 15 11 25t25 11h36q59 0 101 42t42 101v17q0 23-16 38t-38 16h-125q-44 0-76 31t-31 76v214q0 45 31 76t76 32h214q45 0 76-32t32-76z m500 0v-392q0-58-23-111t-61-91-91-61-111-23h-36q-14 0-25 11t-11 25v71q0 15 11 25t25 11h36q59 0 101 42t42 101v17q0 23-16 38t-38 16h-125q-44 0-76 31t-31 76v214q0 45 31 76t76 32h214q45 0 76-32t32-76z" horiz-adv-x="928.6" />
|
||||||
|
|
||||||
|
<glyph glyph-name="reply" unicode="" d="M1000 232q0-93-71-252-1-4-6-13t-7-17-7-12q-7-10-16-10-8 0-13 6t-5 14q0 5 1 15t2 13q3 38 3 69 0 56-10 101t-27 77-45 56-59 39-74 24-86 12-98 3h-125v-143q0-14-10-25t-26-11-25 11l-285 286q-11 10-11 25t11 25l285 286q11 10 25 10t26-10 10-25v-143h125q398 0 488-225 30-75 30-186z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="smile" unicode="" d="M633 257q-21-67-77-109t-127-41-128 41-77 109q-4 14 3 27t21 18q14 4 27-2t17-22q14-44 52-72t85-28 84 28 52 72q4 15 18 22t27 2 21-18 2-27z m-276 243q0-30-21-51t-50-21-51 21-21 51 21 50 51 21 50-21 21-50z m286 0q0-30-21-51t-51-21-50 21-21 51 21 50 50 21 51-21 21-50z m143-143q0 73-29 139t-76 114-114 76-138 28-139-28-114-76-76-114-29-139 29-139 76-113 114-77 139-28 138 28 114 77 76 113 29 139z m71 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="lock-open-alt" unicode="" d="M589 428q23 0 38-15t16-38v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v179q0 103 74 177t176 73 177-73 73-177q0-14-10-25t-25-11h-36q-14 0-25 11t-11 25q0 59-42 101t-101 42-101-42-41-101v-179h410z" horiz-adv-x="642.9" />
|
||||||
|
|
||||||
|
<glyph glyph-name="ellipsis" unicode="" d="M214 446v-107q0-22-15-38t-38-15h-107q-23 0-38 15t-16 38v107q0 23 16 38t38 16h107q22 0 38-16t15-38z m286 0v-107q0-22-16-38t-38-15h-107q-22 0-38 15t-15 38v107q0 23 15 38t38 16h107q23 0 38-16t16-38z m286 0v-107q0-22-16-38t-38-15h-107q-22 0-38 15t-16 38v107q0 23 16 38t38 16h107q23 0 38-16t16-38z" horiz-adv-x="785.7" />
|
||||||
|
|
||||||
|
<glyph glyph-name="play-circled" unicode="" d="M429 786q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m214-460q18 10 18 31t-18 31l-304 178q-17 11-35 1-18-11-18-31v-358q0-20 18-31 9-4 17-4 10 0 18 5z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="thumbs-up-alt" unicode="" d="M143 107q0 15-11 25t-25 11q-15 0-25-11t-11-25q0-15 11-25t25-11q15 0 25 11t11 25z m89 286v-357q0-15-10-25t-26-11h-160q-15 0-25 11t-11 25v357q0 14 11 25t25 10h160q15 0 26-10t10-25z m661 0q0-48-31-83 9-25 9-43 1-42-24-76 9-31 0-66-9-31-31-52 5-62-27-101-36-43-110-44h-72q-37 0-80 9t-68 16-67 22q-69 24-88 25-15 0-25 11t-11 25v357q0 14 10 25t24 11q13 1 42 33t57 67q38 49 56 67 10 10 17 27t10 27 8 34q4 22 7 34t11 29 19 28q10 11 25 11 25 0 46-6t33-15 22-22 14-25 7-28 2-25 1-22q0-21-6-43t-10-33-16-31q-1-4-5-10t-6-13-5-13h155q43 0 75-32t32-75z" horiz-adv-x="928.6" />
|
||||||
|
|
||||||
|
<glyph glyph-name="apple" unicode="" d="M777 179q-21-70-68-139-72-110-144-110-27 0-78 18-48 18-84 18-34 0-79-19-45-19-74-19-85 0-168 145-82 146-82 281 0 127 63 208 63 81 159 81 40 0 98-17 58-17 77-17 25 0 80 19 57 19 97 19 66 0 119-36 29-20 58-56-44-37-64-66-36-52-36-115 0-69 38-125t88-70z m-209 655q0-34-17-76-16-42-52-77-30-30-60-40-20-7-58-10 2 83 44 143 41 60 139 83 1-2 2-6t1-6q0-2 0-6t1-5z" horiz-adv-x="785.7" />
|
||||||
|
|
||||||
|
<glyph glyph-name="android" unicode="" d="M275 588q9 0 16 6t6 15-6 16-16 6-15-6-6-16 6-15 15-6z m236 0q9 0 15 6t6 15-6 16-15 6-16-6-6-16 6-15 16-6z m-453-103q23 0 40-17t16-40v-240q0-24-16-41t-40-17-41 17-17 41v240q0 23 17 40t41 17z m591-11v-371q0-26-18-44t-43-18h-42v-127q0-24-16-40t-41-17-41 17-17 40v127h-77v-127q0-24-16-40t-41-17q-24 0-40 17t-17 40l-1 127h-41q-26 0-43 18t-18 44v371h512z m-129 226q59-30 95-85t36-121h-516q0 66 35 121t96 85l-39 73q-4 8 2 12 8 3 12-4l40-74q53 24 112 24t112-24l40 74q4 7 11 4 7-4 3-12z m266-272v-240q0-24-17-41t-41-17q-23 0-40 17t-17 41v240q0 24 17 40t40 17q24 0 41-17t17-40z" horiz-adv-x="785.7" />
|
||||||
|
|
||||||
|
<glyph glyph-name="paper-plane-empty" unicode="" d="M984 851q19-13 15-36l-142-857q-3-16-18-25-8-5-18-5-6 0-13 3l-294 120-166-182q-10-12-27-12-7 0-12 2-11 4-17 13t-6 21v252l-264 108q-20 8-22 30-2 22 18 33l928 536q20 12 38-1z m-190-837l123 739-800-462 187-76 482 356-267-444z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="binoculars" unicode="" d="M393 678v-428q0-15-11-25t-25-11v-321q0-15-10-25t-26-11h-285q-15 0-25 11t-11 25v285l139 488q4 12 17 12h237z m178 0v-392h-142v392h142z m429-500v-285q0-15-11-25t-25-11h-285q-15 0-25 11t-11 25v321q-15 0-25 11t-11 25v428h237q13 0 17-12z m-589 661v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z m375 0v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="user-plus" unicode="" d="M393 357q-89 0-152 63t-62 151 62 152 152 63 151-63 63-152-63-151-151-63z m536-71h196q7 0 13-6t5-12v-107q0-8-5-13t-13-5h-196v-197q0-7-6-12t-12-6h-107q-8 0-13 6t-5 12v197h-197q-7 0-12 5t-6 13v107q0 7 6 12t12 6h197v196q0 7 5 13t13 5h107q7 0 12-5t6-13v-196z m-411-125q0-29 21-51t50-21h143v-133q-38-28-95-28h-488q-67 0-108 39t-41 106q0 30 2 58t8 61 15 60 24 55 34 45 48 30 62 11q11 0 22-10 44-34 86-51t92-17 92 17 86 51q11 10 22 10 73 0 121-54h-125q-29 0-50-21t-21-50v-107z" horiz-adv-x="1142.9" />
|
||||||
|
|
||||||
|
<glyph glyph-name="hashtag" unicode="" d="M553 286l36 142h-142l-36-142h142z m429 281l-32-125q-4-14-17-14h-182l-36-142h173q9 0 14-7 6-8 4-16l-32-125q-2-13-17-13h-182l-45-183q-4-14-18-14h-125q-9 0-14 7-5 7-4 16l44 174h-142l-45-183q-4-14-17-14h-126q-8 0-14 7-5 7-3 16l43 174h-173q-9 0-14 7-5 6-4 15l32 125q4 14 17 14h182l36 142h-173q-9 0-14 7-6 8-4 16l32 125q2 13 17 13h182l46 183q3 14 17 14h125q9 0 14-7 5-7 4-16l-44-174h142l45 183q4 14 18 14h125q8 0 14-7 5-7 3-16l-43-174h173q9 0 14-7 5-6 4-15z" horiz-adv-x="1000" />
|
||||||
|
</font>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 28 KiB |
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 lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/vendors~app.b2603a50868c68a1c192.css rel=stylesheet><link href=/static/css/app.ae04505b31bb0ee2765e.css rel=stylesheet><link href=/static/fontello.1580232989700.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.9ab182239f3a2abee89f.js></script><script type=text/javascript src=/static/js/app.9cfed8f3d06c299128ea.js></script></body></html>
|
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/vendors~app.b2603a50868c68a1c192.css rel=stylesheet><link href=/static/css/app.ae04505b31bb0ee2765e.css rel=stylesheet><link href=/static/fontello.1581007281335.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.c26cf2fc57e9c1975e8d.js></script><script type=text/javascript src=/static/js/app.0aac253187b2af873849.js></script></body></html>
|
|
@ -29,7 +29,7 @@
|
||||||
"@id": "litepub:oauthRegistrationEndpoint",
|
"@id": "litepub:oauthRegistrationEndpoint",
|
||||||
"@type": "@id"
|
"@type": "@id"
|
||||||
},
|
},
|
||||||
"EmojiReaction": "litepub:EmojiReaction",
|
"EmojiReact": "litepub:EmojiReact",
|
||||||
"alsoKnownAs": {
|
"alsoKnownAs": {
|
||||||
"@id": "as:alsoKnownAs",
|
"@id": "as:alsoKnownAs",
|
||||||
"@type": "@id"
|
"@type": "@id"
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
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.
|
@ -0,0 +1,28 @@
|
||||||
|
defmodule Restarter.Pleroma do
|
||||||
|
use GenServer
|
||||||
|
|
||||||
|
def start_link(_) do
|
||||||
|
GenServer.start_link(__MODULE__, [], name: __MODULE__)
|
||||||
|
end
|
||||||
|
|
||||||
|
def init(_), do: {:ok, %{}}
|
||||||
|
|
||||||
|
def handle_info(:after_boot, %{after_boot: true} = state), do: {:noreply, state}
|
||||||
|
|
||||||
|
def handle_info(:after_boot, state) do
|
||||||
|
restart(:pleroma)
|
||||||
|
{:noreply, Map.put(state, :after_boot, true)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info({:restart, delay}, state) do
|
||||||
|
Process.sleep(delay)
|
||||||
|
restart(:pleroma)
|
||||||
|
{:noreply, state}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restart(app) do
|
||||||
|
:ok = Application.ensure_started(app)
|
||||||
|
:ok = Application.stop(app)
|
||||||
|
:ok = Application.start(app)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,8 @@
|
||||||
|
defmodule Restarter do
|
||||||
|
use Application
|
||||||
|
|
||||||
|
def start(_, _) do
|
||||||
|
opts = [strategy: :one_for_one, name: Restarter.Supervisor]
|
||||||
|
Supervisor.start_link([Restarter.Pleroma], opts)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,21 @@
|
||||||
|
defmodule Restarter.MixProject do
|
||||||
|
use Mix.Project
|
||||||
|
|
||||||
|
def project do
|
||||||
|
[
|
||||||
|
app: :restarter,
|
||||||
|
version: "0.1.0",
|
||||||
|
elixir: "~> 1.8",
|
||||||
|
start_permanent: Mix.env() == :prod,
|
||||||
|
deps: deps()
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def application do
|
||||||
|
[
|
||||||
|
mod: {Restarter, []}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp deps, do: []
|
||||||
|
end
|
|
@ -5,6 +5,8 @@
|
||||||
defmodule Pleroma.Config.TransferTaskTest do
|
defmodule Pleroma.Config.TransferTaskTest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
import ExUnit.CaptureLog
|
||||||
|
|
||||||
alias Pleroma.Config.TransferTask
|
alias Pleroma.Config.TransferTask
|
||||||
alias Pleroma.ConfigDB
|
alias Pleroma.ConfigDB
|
||||||
|
|
||||||
|
@ -105,4 +107,75 @@ test "transfer config values with full subkey update" do
|
||||||
Application.put_env(:pleroma, :assets, assets)
|
Application.put_env(:pleroma, :assets, assets)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "pleroma restart" do
|
||||||
|
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)
|
||||||
|
|
||||||
|
ConfigDB.create(%{
|
||||||
|
group: ":pleroma",
|
||||||
|
key: ":emoji",
|
||||||
|
value: [groups: [a: 1, b: 2]]
|
||||||
|
})
|
||||||
|
|
||||||
|
refute String.contains?(
|
||||||
|
capture_log(fn -> TransferTask.start_link([]) end),
|
||||||
|
"pleroma restarted"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "restart pleroma on reboot time key" do
|
||||||
|
chat = Application.get_env(:pleroma, :chat)
|
||||||
|
on_exit(fn -> Application.put_env(:pleroma, :chat, chat) end)
|
||||||
|
|
||||||
|
ConfigDB.create(%{
|
||||||
|
group: ":pleroma",
|
||||||
|
key: ":chat",
|
||||||
|
value: [enabled: false]
|
||||||
|
})
|
||||||
|
|
||||||
|
assert capture_log(fn -> TransferTask.start_link([]) end) =~ "pleroma restarted"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "restart pleroma on reboot time subkey" do
|
||||||
|
captcha = Application.get_env(:pleroma, Pleroma.Captcha)
|
||||||
|
on_exit(fn -> Application.put_env(:pleroma, Pleroma.Captcha, captcha) end)
|
||||||
|
|
||||||
|
ConfigDB.create(%{
|
||||||
|
group: ":pleroma",
|
||||||
|
key: "Pleroma.Captcha",
|
||||||
|
value: [seconds_valid: 60]
|
||||||
|
})
|
||||||
|
|
||||||
|
assert capture_log(fn -> TransferTask.start_link([]) end) =~ "pleroma restarted"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "don't restart pleroma on reboot time key and subkey if there is false flag" do
|
||||||
|
chat = Application.get_env(:pleroma, :chat)
|
||||||
|
captcha = Application.get_env(:pleroma, Pleroma.Captcha)
|
||||||
|
|
||||||
|
on_exit(fn ->
|
||||||
|
Application.put_env(:pleroma, :chat, chat)
|
||||||
|
Application.put_env(:pleroma, Pleroma.Captcha, captcha)
|
||||||
|
end)
|
||||||
|
|
||||||
|
ConfigDB.create(%{
|
||||||
|
group: ":pleroma",
|
||||||
|
key: ":chat",
|
||||||
|
value: [enabled: false]
|
||||||
|
})
|
||||||
|
|
||||||
|
ConfigDB.create(%{
|
||||||
|
group: ":pleroma",
|
||||||
|
key: "Pleroma.Captcha",
|
||||||
|
value: [seconds_valid: 60]
|
||||||
|
})
|
||||||
|
|
||||||
|
refute String.contains?(
|
||||||
|
capture_log(fn -> TransferTask.load_and_update_env([], false) end),
|
||||||
|
"pleroma restarted"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"type": "EmojiReact",
|
||||||
|
"signature": {
|
||||||
|
"type": "RsaSignature2017",
|
||||||
|
"signatureValue": "fdxMfQSMwbC6wP6sh6neS/vM5879K67yQkHTbiT5Npr5wAac0y6+o3Ij+41tN3rL6wfuGTosSBTHOtta6R4GCOOhCaCSLMZKypnp1VltCzLDoyrZELnYQIC8gpUXVmIycZbREk22qWUe/w7DAFaKK4UscBlHDzeDVcA0K3Se5Sluqi9/Zh+ldAnEzj/rSEPDjrtvf5wGNf3fHxbKSRKFt90JvKK6hS+vxKUhlRFDf6/SMETw+EhwJSNW4d10yMUakqUWsFv4Acq5LW7l+HpYMvlYY1FZhNde1+uonnCyuQDyvzkff8zwtEJmAXC4RivO/VVLa17SmqheJZfI8oluVg==",
|
||||||
|
"creator": "http://mastodon.example.org/users/admin#main-key",
|
||||||
|
"created": "2018-02-17T18:57:49Z"
|
||||||
|
},
|
||||||
|
"object": "http://localtesting.pleroma.lol/objects/eb92579d-3417-42a8-8652-2492c2d4f454",
|
||||||
|
"content": "~",
|
||||||
|
"nickname": "lain",
|
||||||
|
"id": "http://mastodon.example.org/users/admin#reactions/2",
|
||||||
|
"actor": "http://mastodon.example.org/users/admin",
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://w3id.org/security/v1",
|
||||||
|
{
|
||||||
|
"toot": "http://joinmastodon.org/ns#",
|
||||||
|
"sensitive": "as:sensitive",
|
||||||
|
"ostatus": "http://ostatus.org#",
|
||||||
|
"movedTo": "as:movedTo",
|
||||||
|
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||||
|
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||||
|
"conversation": "ostatus:conversation",
|
||||||
|
"atomUri": "ostatus:atomUri",
|
||||||
|
"Hashtag": "as:Hashtag",
|
||||||
|
"Emoji": "toot:Emoji"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"type": "EmojiReact",
|
||||||
|
"signature": {
|
||||||
|
"type": "RsaSignature2017",
|
||||||
|
"signatureValue": "fdxMfQSMwbC6wP6sh6neS/vM5879K67yQkHTbiT5Npr5wAac0y6+o3Ij+41tN3rL6wfuGTosSBTHOtta6R4GCOOhCaCSLMZKypnp1VltCzLDoyrZELnYQIC8gpUXVmIycZbREk22qWUe/w7DAFaKK4UscBlHDzeDVcA0K3Se5Sluqi9/Zh+ldAnEzj/rSEPDjrtvf5wGNf3fHxbKSRKFt90JvKK6hS+vxKUhlRFDf6/SMETw+EhwJSNW4d10yMUakqUWsFv4Acq5LW7l+HpYMvlYY1FZhNde1+uonnCyuQDyvzkff8zwtEJmAXC4RivO/VVLa17SmqheJZfI8oluVg==",
|
||||||
|
"creator": "http://mastodon.example.org/users/admin#main-key",
|
||||||
|
"created": "2018-02-17T18:57:49Z"
|
||||||
|
},
|
||||||
|
"object": "http://localtesting.pleroma.lol/objects/eb92579d-3417-42a8-8652-2492c2d4f454",
|
||||||
|
"content": "👌👌",
|
||||||
|
"nickname": "lain",
|
||||||
|
"id": "http://mastodon.example.org/users/admin#reactions/2",
|
||||||
|
"actor": "http://mastodon.example.org/users/admin",
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://w3id.org/security/v1",
|
||||||
|
{
|
||||||
|
"toot": "http://joinmastodon.org/ns#",
|
||||||
|
"sensitive": "as:sensitive",
|
||||||
|
"ostatus": "http://ostatus.org#",
|
||||||
|
"movedTo": "as:movedTo",
|
||||||
|
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||||
|
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||||
|
"conversation": "ostatus:conversation",
|
||||||
|
"atomUri": "ostatus:atomUri",
|
||||||
|
"Hashtag": "as:Hashtag",
|
||||||
|
"Emoji": "toot:Emoji"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"type": "EmojiReaction",
|
"type": "EmojiReact",
|
||||||
"signature": {
|
"signature": {
|
||||||
"type": "RsaSignature2017",
|
"type": "RsaSignature2017",
|
||||||
"signatureValue": "fdxMfQSMwbC6wP6sh6neS/vM5879K67yQkHTbiT5Npr5wAac0y6+o3Ij+41tN3rL6wfuGTosSBTHOtta6R4GCOOhCaCSLMZKypnp1VltCzLDoyrZELnYQIC8gpUXVmIycZbREk22qWUe/w7DAFaKK4UscBlHDzeDVcA0K3Se5Sluqi9/Zh+ldAnEzj/rSEPDjrtvf5wGNf3fHxbKSRKFt90JvKK6hS+vxKUhlRFDf6/SMETw+EhwJSNW4d10yMUakqUWsFv4Acq5LW7l+HpYMvlYY1FZhNde1+uonnCyuQDyvzkff8zwtEJmAXC4RivO/VVLa17SmqheJZfI8oluVg==",
|
"signatureValue": "fdxMfQSMwbC6wP6sh6neS/vM5879K67yQkHTbiT5Npr5wAac0y6+o3Ij+41tN3rL6wfuGTosSBTHOtta6R4GCOOhCaCSLMZKypnp1VltCzLDoyrZELnYQIC8gpUXVmIycZbREk22qWUe/w7DAFaKK4UscBlHDzeDVcA0K3Se5Sluqi9/Zh+ldAnEzj/rSEPDjrtvf5wGNf3fHxbKSRKFt90JvKK6hS+vxKUhlRFDf6/SMETw+EhwJSNW4d10yMUakqUWsFv4Acq5LW7l+HpYMvlYY1FZhNde1+uonnCyuQDyvzkff8zwtEJmAXC4RivO/VVLa17SmqheJZfI8oluVg==",
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -76,8 +76,9 @@ test "ensures cache is cleared for the object" do
|
||||||
describe "delete attachments" do
|
describe "delete attachments" do
|
||||||
clear_config([Pleroma.Upload])
|
clear_config([Pleroma.Upload])
|
||||||
|
|
||||||
test "in subdirectories" do
|
test "Disabled via config" do
|
||||||
Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
|
Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
|
||||||
|
Pleroma.Config.put([:instance, :cleanup_attachments], false)
|
||||||
|
|
||||||
file = %Plug.Upload{
|
file = %Plug.Upload{
|
||||||
content_type: "image/jpg",
|
content_type: "image/jpg",
|
||||||
|
@ -103,6 +104,41 @@ test "in subdirectories" do
|
||||||
|
|
||||||
ObanHelpers.perform(all_enqueued(worker: Pleroma.Workers.AttachmentsCleanupWorker))
|
ObanHelpers.perform(all_enqueued(worker: Pleroma.Workers.AttachmentsCleanupWorker))
|
||||||
|
|
||||||
|
assert Object.get_by_id(note.id).data["deleted"]
|
||||||
|
refute Object.get_by_id(attachment.id) == nil
|
||||||
|
|
||||||
|
assert {:ok, ["an_image.jpg"]} == File.ls("#{uploads_dir}/#{path}")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "in subdirectories" do
|
||||||
|
Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
|
||||||
|
Pleroma.Config.put([:instance, :cleanup_attachments], true)
|
||||||
|
|
||||||
|
file = %Plug.Upload{
|
||||||
|
content_type: "image/jpg",
|
||||||
|
path: Path.absname("test/fixtures/image.jpg"),
|
||||||
|
filename: "an_image.jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, %Object{} = attachment} =
|
||||||
|
Pleroma.Web.ActivityPub.ActivityPub.upload(file, actor: user.ap_id)
|
||||||
|
|
||||||
|
%{data: %{"attachment" => [%{"url" => [%{"href" => href}]}]}} =
|
||||||
|
note = insert(:note, %{user: user, data: %{"attachment" => [attachment.data]}})
|
||||||
|
|
||||||
|
uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads])
|
||||||
|
|
||||||
|
path = href |> Path.dirname() |> Path.basename()
|
||||||
|
|
||||||
|
assert {:ok, ["an_image.jpg"]} == File.ls("#{uploads_dir}/#{path}")
|
||||||
|
|
||||||
|
Object.delete(note)
|
||||||
|
|
||||||
|
ObanHelpers.perform(all_enqueued(worker: Pleroma.Workers.AttachmentsCleanupWorker))
|
||||||
|
|
||||||
|
assert Object.get_by_id(note.id).data["deleted"]
|
||||||
assert Object.get_by_id(attachment.id) == nil
|
assert Object.get_by_id(attachment.id) == nil
|
||||||
|
|
||||||
assert {:ok, []} == File.ls("#{uploads_dir}/#{path}")
|
assert {:ok, []} == File.ls("#{uploads_dir}/#{path}")
|
||||||
|
@ -111,6 +147,7 @@ test "in subdirectories" do
|
||||||
test "with dedupe enabled" do
|
test "with dedupe enabled" do
|
||||||
Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
|
Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
|
||||||
Pleroma.Config.put([Pleroma.Upload, :filters], [Pleroma.Upload.Filter.Dedupe])
|
Pleroma.Config.put([Pleroma.Upload, :filters], [Pleroma.Upload.Filter.Dedupe])
|
||||||
|
Pleroma.Config.put([:instance, :cleanup_attachments], true)
|
||||||
|
|
||||||
uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads])
|
uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads])
|
||||||
|
|
||||||
|
@ -139,6 +176,7 @@ test "with dedupe enabled" do
|
||||||
|
|
||||||
ObanHelpers.perform(all_enqueued(worker: Pleroma.Workers.AttachmentsCleanupWorker))
|
ObanHelpers.perform(all_enqueued(worker: Pleroma.Workers.AttachmentsCleanupWorker))
|
||||||
|
|
||||||
|
assert Object.get_by_id(note.id).data["deleted"]
|
||||||
assert Object.get_by_id(attachment.id) == nil
|
assert Object.get_by_id(attachment.id) == nil
|
||||||
assert {:ok, files} = File.ls(uploads_dir)
|
assert {:ok, files} = File.ls(uploads_dir)
|
||||||
refute filename in files
|
refute filename in files
|
||||||
|
@ -146,6 +184,7 @@ test "with dedupe enabled" do
|
||||||
|
|
||||||
test "with objects that have legacy data.url attribute" do
|
test "with objects that have legacy data.url attribute" do
|
||||||
Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
|
Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
|
||||||
|
Pleroma.Config.put([:instance, :cleanup_attachments], true)
|
||||||
|
|
||||||
file = %Plug.Upload{
|
file = %Plug.Upload{
|
||||||
content_type: "image/jpg",
|
content_type: "image/jpg",
|
||||||
|
@ -173,6 +212,7 @@ test "with objects that have legacy data.url attribute" do
|
||||||
|
|
||||||
ObanHelpers.perform(all_enqueued(worker: Pleroma.Workers.AttachmentsCleanupWorker))
|
ObanHelpers.perform(all_enqueued(worker: Pleroma.Workers.AttachmentsCleanupWorker))
|
||||||
|
|
||||||
|
assert Object.get_by_id(note.id).data["deleted"]
|
||||||
assert Object.get_by_id(attachment.id) == nil
|
assert Object.get_by_id(attachment.id) == nil
|
||||||
|
|
||||||
assert {:ok, []} == File.ls("#{uploads_dir}/#{path}")
|
assert {:ok, []} == File.ls("#{uploads_dir}/#{path}")
|
||||||
|
@ -181,6 +221,7 @@ test "with objects that have legacy data.url attribute" do
|
||||||
test "With custom base_url" do
|
test "With custom base_url" do
|
||||||
Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
|
Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
|
||||||
Pleroma.Config.put([Pleroma.Upload, :base_url], "https://sub.domain.tld/dir/")
|
Pleroma.Config.put([Pleroma.Upload, :base_url], "https://sub.domain.tld/dir/")
|
||||||
|
Pleroma.Config.put([:instance, :cleanup_attachments], true)
|
||||||
|
|
||||||
file = %Plug.Upload{
|
file = %Plug.Upload{
|
||||||
content_type: "image/jpg",
|
content_type: "image/jpg",
|
||||||
|
@ -206,6 +247,7 @@ test "With custom base_url" do
|
||||||
|
|
||||||
ObanHelpers.perform(all_enqueued(worker: Pleroma.Workers.AttachmentsCleanupWorker))
|
ObanHelpers.perform(all_enqueued(worker: Pleroma.Workers.AttachmentsCleanupWorker))
|
||||||
|
|
||||||
|
assert Object.get_by_id(note.id).data["deleted"]
|
||||||
assert Object.get_by_id(attachment.id) == nil
|
assert Object.get_by_id(attachment.id) == nil
|
||||||
|
|
||||||
assert {:ok, []} == File.ls("#{uploads_dir}/#{path}")
|
assert {:ok, []} == File.ls("#{uploads_dir}/#{path}")
|
||||||
|
|
|
@ -16,6 +16,7 @@ defmodule Pleroma.Plugs.RateLimiterTest do
|
||||||
test "config is required for plug to work" do
|
test "config is required for plug to work" do
|
||||||
limiter_name = :test_init
|
limiter_name = :test_init
|
||||||
Pleroma.Config.put([:rate_limit, limiter_name], {1, 1})
|
Pleroma.Config.put([:rate_limit, limiter_name], {1, 1})
|
||||||
|
Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
|
||||||
|
|
||||||
assert %{limits: {1, 1}, name: :test_init, opts: [name: :test_init]} ==
|
assert %{limits: {1, 1}, name: :test_init, opts: [name: :test_init]} ==
|
||||||
RateLimiter.init(name: limiter_name)
|
RateLimiter.init(name: limiter_name)
|
||||||
|
@ -23,11 +24,39 @@ test "config is required for plug to work" do
|
||||||
assert nil == RateLimiter.init(name: :foo)
|
assert nil == RateLimiter.init(name: :foo)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it is disabled for localhost" do
|
||||||
|
limiter_name = :test_init
|
||||||
|
Pleroma.Config.put([:rate_limit, limiter_name], {1, 1})
|
||||||
|
Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {127, 0, 0, 1})
|
||||||
|
Pleroma.Config.put([Pleroma.Plugs.RemoteIp, :enabled], false)
|
||||||
|
|
||||||
|
assert RateLimiter.disabled?() == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it is disabled for socket" do
|
||||||
|
limiter_name = :test_init
|
||||||
|
Pleroma.Config.put([:rate_limit, limiter_name], {1, 1})
|
||||||
|
Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {:local, "/path/to/pleroma.sock"})
|
||||||
|
Pleroma.Config.put([Pleroma.Plugs.RemoteIp, :enabled], false)
|
||||||
|
|
||||||
|
assert RateLimiter.disabled?() == true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it is enabled for socket when remote ip is enabled" do
|
||||||
|
limiter_name = :test_init
|
||||||
|
Pleroma.Config.put([:rate_limit, limiter_name], {1, 1})
|
||||||
|
Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {:local, "/path/to/pleroma.sock"})
|
||||||
|
Pleroma.Config.put([Pleroma.Plugs.RemoteIp, :enabled], true)
|
||||||
|
|
||||||
|
assert RateLimiter.disabled?() == false
|
||||||
|
end
|
||||||
|
|
||||||
test "it restricts based on config values" do
|
test "it restricts based on config values" do
|
||||||
limiter_name = :test_opts
|
limiter_name = :test_opts
|
||||||
scale = 80
|
scale = 80
|
||||||
limit = 5
|
limit = 5
|
||||||
|
|
||||||
|
Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
|
||||||
Pleroma.Config.put([:rate_limit, limiter_name], {scale, limit})
|
Pleroma.Config.put([:rate_limit, limiter_name], {scale, limit})
|
||||||
|
|
||||||
opts = RateLimiter.init(name: limiter_name)
|
opts = RateLimiter.init(name: limiter_name)
|
||||||
|
@ -61,6 +90,7 @@ test "`bucket_name` option overrides default bucket name" do
|
||||||
limiter_name = :test_bucket_name
|
limiter_name = :test_bucket_name
|
||||||
|
|
||||||
Pleroma.Config.put([:rate_limit, limiter_name], {1000, 5})
|
Pleroma.Config.put([:rate_limit, limiter_name], {1000, 5})
|
||||||
|
Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
|
||||||
|
|
||||||
base_bucket_name = "#{limiter_name}:group1"
|
base_bucket_name = "#{limiter_name}:group1"
|
||||||
opts = RateLimiter.init(name: limiter_name, bucket_name: base_bucket_name)
|
opts = RateLimiter.init(name: limiter_name, bucket_name: base_bucket_name)
|
||||||
|
@ -75,6 +105,7 @@ test "`bucket_name` option overrides default bucket name" do
|
||||||
test "`params` option allows different queries to be tracked independently" do
|
test "`params` option allows different queries to be tracked independently" do
|
||||||
limiter_name = :test_params
|
limiter_name = :test_params
|
||||||
Pleroma.Config.put([:rate_limit, limiter_name], {1000, 5})
|
Pleroma.Config.put([:rate_limit, limiter_name], {1000, 5})
|
||||||
|
Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
|
||||||
|
|
||||||
opts = RateLimiter.init(name: limiter_name, params: ["id"])
|
opts = RateLimiter.init(name: limiter_name, params: ["id"])
|
||||||
|
|
||||||
|
@ -90,6 +121,7 @@ test "`params` option allows different queries to be tracked independently" do
|
||||||
test "it supports combination of options modifying bucket name" do
|
test "it supports combination of options modifying bucket name" do
|
||||||
limiter_name = :test_options_combo
|
limiter_name = :test_options_combo
|
||||||
Pleroma.Config.put([:rate_limit, limiter_name], {1000, 5})
|
Pleroma.Config.put([:rate_limit, limiter_name], {1000, 5})
|
||||||
|
Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
|
||||||
|
|
||||||
base_bucket_name = "#{limiter_name}:group1"
|
base_bucket_name = "#{limiter_name}:group1"
|
||||||
opts = RateLimiter.init(name: limiter_name, bucket_name: base_bucket_name, params: ["id"])
|
opts = RateLimiter.init(name: limiter_name, bucket_name: base_bucket_name, params: ["id"])
|
||||||
|
@ -109,6 +141,7 @@ test "it supports combination of options modifying bucket name" do
|
||||||
test "are restricted based on remote IP" do
|
test "are restricted based on remote IP" do
|
||||||
limiter_name = :test_unauthenticated
|
limiter_name = :test_unauthenticated
|
||||||
Pleroma.Config.put([:rate_limit, limiter_name], [{1000, 5}, {1, 10}])
|
Pleroma.Config.put([:rate_limit, limiter_name], [{1000, 5}, {1, 10}])
|
||||||
|
Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
|
||||||
|
|
||||||
opts = RateLimiter.init(name: limiter_name)
|
opts = RateLimiter.init(name: limiter_name)
|
||||||
|
|
||||||
|
@ -147,6 +180,7 @@ test "can have limits seperate from unauthenticated connections" do
|
||||||
|
|
||||||
scale = 50
|
scale = 50
|
||||||
limit = 5
|
limit = 5
|
||||||
|
Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
|
||||||
Pleroma.Config.put([:rate_limit, limiter_name], [{1000, 1}, {scale, limit}])
|
Pleroma.Config.put([:rate_limit, limiter_name], [{1000, 1}, {scale, limit}])
|
||||||
|
|
||||||
opts = RateLimiter.init(name: limiter_name)
|
opts = RateLimiter.init(name: limiter_name)
|
||||||
|
@ -169,6 +203,7 @@ test "can have limits seperate from unauthenticated connections" do
|
||||||
test "diffrerent users are counted independently" do
|
test "diffrerent users are counted independently" do
|
||||||
limiter_name = :test_authenticated
|
limiter_name = :test_authenticated
|
||||||
Pleroma.Config.put([:rate_limit, limiter_name], [{1, 10}, {1000, 5}])
|
Pleroma.Config.put([:rate_limit, limiter_name], [{1, 10}, {1000, 5}])
|
||||||
|
Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
|
||||||
|
|
||||||
opts = RateLimiter.init(name: limiter_name)
|
opts = RateLimiter.init(name: limiter_name)
|
||||||
|
|
||||||
|
|
|
@ -877,7 +877,7 @@ test "adds an emoji reaction activity to the db" do
|
||||||
assert reaction_activity
|
assert reaction_activity
|
||||||
|
|
||||||
assert reaction_activity.data["actor"] == reactor.ap_id
|
assert reaction_activity.data["actor"] == reactor.ap_id
|
||||||
assert reaction_activity.data["type"] == "EmojiReaction"
|
assert reaction_activity.data["type"] == "EmojiReact"
|
||||||
assert reaction_activity.data["content"] == "🔥"
|
assert reaction_activity.data["content"] == "🔥"
|
||||||
assert reaction_activity.data["object"] == object.data["id"]
|
assert reaction_activity.data["object"] == object.data["id"]
|
||||||
assert reaction_activity.data["to"] == [User.ap_followers(reactor), activity.data["actor"]]
|
assert reaction_activity.data["to"] == [User.ap_followers(reactor), activity.data["actor"]]
|
||||||
|
@ -1174,6 +1174,23 @@ test "creates an undo activity for the last follow" do
|
||||||
assert embedded_object["object"] == followed.ap_id
|
assert embedded_object["object"] == followed.ap_id
|
||||||
assert embedded_object["id"] == follow_activity.data["id"]
|
assert embedded_object["id"] == follow_activity.data["id"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "creates an undo activity for a pending follow request" do
|
||||||
|
follower = insert(:user)
|
||||||
|
followed = insert(:user, %{locked: true})
|
||||||
|
|
||||||
|
{:ok, follow_activity} = ActivityPub.follow(follower, followed)
|
||||||
|
{:ok, activity} = ActivityPub.unfollow(follower, followed)
|
||||||
|
|
||||||
|
assert activity.data["type"] == "Undo"
|
||||||
|
assert activity.data["actor"] == follower.ap_id
|
||||||
|
|
||||||
|
embedded_object = activity.data["object"]
|
||||||
|
assert is_map(embedded_object)
|
||||||
|
assert embedded_object["type"] == "Follow"
|
||||||
|
assert embedded_object["object"] == followed.ap_id
|
||||||
|
assert embedded_object["id"] == follow_activity.data["id"]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "blocking / unblocking" do
|
describe "blocking / unblocking" do
|
||||||
|
|
|
@ -340,7 +340,7 @@ test "it works for incoming likes" do
|
||||||
assert data["object"] == activity.data["object"]
|
assert data["object"] == activity.data["object"]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works for incoming misskey likes, turning them into EmojiReactions" do
|
test "it works for incoming misskey likes, turning them into EmojiReacts" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
|
||||||
|
|
||||||
|
@ -352,13 +352,13 @@ test "it works for incoming misskey likes, turning them into EmojiReactions" do
|
||||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
assert data["actor"] == data["actor"]
|
assert data["actor"] == data["actor"]
|
||||||
assert data["type"] == "EmojiReaction"
|
assert data["type"] == "EmojiReact"
|
||||||
assert data["id"] == data["id"]
|
assert data["id"] == data["id"]
|
||||||
assert data["object"] == activity.data["object"]
|
assert data["object"] == activity.data["object"]
|
||||||
assert data["content"] == "🍮"
|
assert data["content"] == "🍮"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works for incoming misskey likes that contain unicode emojis, turning them into EmojiReactions" do
|
test "it works for incoming misskey likes that contain unicode emojis, turning them into EmojiReacts" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
|
||||||
|
|
||||||
|
@ -371,7 +371,7 @@ test "it works for incoming misskey likes that contain unicode emojis, turning t
|
||||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
assert data["actor"] == data["actor"]
|
assert data["actor"] == data["actor"]
|
||||||
assert data["type"] == "EmojiReaction"
|
assert data["type"] == "EmojiReact"
|
||||||
assert data["id"] == data["id"]
|
assert data["id"] == data["id"]
|
||||||
assert data["object"] == activity.data["object"]
|
assert data["object"] == activity.data["object"]
|
||||||
assert data["content"] == "⭐"
|
assert data["content"] == "⭐"
|
||||||
|
@ -389,12 +389,31 @@ test "it works for incoming emoji reactions" do
|
||||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
||||||
assert data["type"] == "EmojiReaction"
|
assert data["type"] == "EmojiReact"
|
||||||
assert data["id"] == "http://mastodon.example.org/users/admin#reactions/2"
|
assert data["id"] == "http://mastodon.example.org/users/admin#reactions/2"
|
||||||
assert data["object"] == activity.data["object"]
|
assert data["object"] == activity.data["object"]
|
||||||
assert data["content"] == "👌"
|
assert data["content"] == "👌"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it reject invalid emoji reactions" do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
|
||||||
|
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/emoji-reaction-too-long.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("object", activity.data["object"])
|
||||||
|
|
||||||
|
assert :error = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/emoji-reaction-no-emoji.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("object", activity.data["object"])
|
||||||
|
|
||||||
|
assert :error = Transmogrifier.handle_incoming(data)
|
||||||
|
end
|
||||||
|
|
||||||
test "it works for incoming emoji reaction undos" do
|
test "it works for incoming emoji reaction undos" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
|
|
|
@ -1899,13 +1899,6 @@ test "when configuration from database is off", %{conn: conn} do
|
||||||
"To use this endpoint you need to enable configuration from database."
|
"To use this endpoint you need to enable configuration from database."
|
||||||
end
|
end
|
||||||
|
|
||||||
test "without any settings in db", %{conn: conn} do
|
|
||||||
conn = get(conn, "/api/pleroma/admin/config")
|
|
||||||
|
|
||||||
assert json_response(conn, 400) ==
|
|
||||||
"To use configuration from database migrate your settings to database."
|
|
||||||
end
|
|
||||||
|
|
||||||
test "with settings only in db", %{conn: conn} do
|
test "with settings only in db", %{conn: conn} do
|
||||||
config1 = insert(:config)
|
config1 = insert(:config)
|
||||||
config2 = insert(:config)
|
config2 = insert(:config)
|
||||||
|
@ -2043,7 +2036,6 @@ test "POST /api/pleroma/admin/config error", %{conn: conn} do
|
||||||
Application.delete_env(:pleroma, Pleroma.Captcha.NotReal)
|
Application.delete_env(:pleroma, Pleroma.Captcha.NotReal)
|
||||||
Application.put_env(:pleroma, :http, http)
|
Application.put_env(:pleroma, :http, http)
|
||||||
Application.put_env(:tesla, :adapter, Tesla.Mock)
|
Application.put_env(:tesla, :adapter, Tesla.Mock)
|
||||||
:ok = File.rm("config/test.exported_from_db.secret.exs")
|
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -2170,7 +2162,7 @@ test "create new config setting in db", %{conn: conn} do
|
||||||
assert Application.get_env(:idna, :key5) == {"string", Pleroma.Captcha.NotReal, []}
|
assert Application.get_env(:idna, :key5) == {"string", Pleroma.Captcha.NotReal, []}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "save config setting without key", %{conn: conn} do
|
test "save configs setting without explicit key", %{conn: conn} do
|
||||||
level = Application.get_env(:quack, :level)
|
level = Application.get_env(:quack, :level)
|
||||||
meta = Application.get_env(:quack, :meta)
|
meta = Application.get_env(:quack, :meta)
|
||||||
webhook_url = Application.get_env(:quack, :webhook_url)
|
webhook_url = Application.get_env(:quack, :webhook_url)
|
||||||
|
@ -2256,6 +2248,34 @@ test "saving config with partial update", %{conn: conn} do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "saving config which need pleroma reboot", %{conn: conn} do
|
||||||
|
chat = Pleroma.Config.get(:chat)
|
||||||
|
on_exit(fn -> Pleroma.Config.put(:chat, chat) end)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
post(
|
||||||
|
conn,
|
||||||
|
"/api/pleroma/admin/config",
|
||||||
|
%{
|
||||||
|
configs: [
|
||||||
|
%{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert json_response(conn, 200) == %{
|
||||||
|
"configs" => [
|
||||||
|
%{
|
||||||
|
"db" => [":enabled"],
|
||||||
|
"group" => ":pleroma",
|
||||||
|
"key" => ":chat",
|
||||||
|
"value" => [%{"tuple" => [":enabled", true]}]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"need_reboot" => true
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
test "saving config with nested merge", %{conn: conn} do
|
test "saving config with nested merge", %{conn: conn} do
|
||||||
config =
|
config =
|
||||||
insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: [k1: 1, k2: 2]))
|
insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: [k1: 1, k2: 2]))
|
||||||
|
@ -2957,47 +2977,15 @@ test "proxy tuple ip", %{conn: conn} do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "config mix tasks run" do
|
describe "GET /api/pleroma/admin/restart" do
|
||||||
setup do
|
|
||||||
Mix.shell(Mix.Shell.Quiet)
|
|
||||||
|
|
||||||
on_exit(fn ->
|
|
||||||
Mix.shell(Mix.Shell.IO)
|
|
||||||
end)
|
|
||||||
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
|
|
||||||
clear_config(:configurable_from_database) do
|
clear_config(:configurable_from_database) do
|
||||||
Pleroma.Config.put(:configurable_from_database, true)
|
Pleroma.Config.put(:configurable_from_database, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
clear_config([:feed, :post_title]) do
|
test "pleroma restarts", %{conn: conn} do
|
||||||
Pleroma.Config.put([:feed, :post_title], %{max_length: 100, omission: "…"})
|
ExUnit.CaptureLog.capture_log(fn ->
|
||||||
end
|
assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
|
||||||
|
end) =~ "pleroma restarted"
|
||||||
test "transfer settings to DB and to file", %{conn: conn} do
|
|
||||||
assert Repo.all(Pleroma.ConfigDB) == []
|
|
||||||
Mix.Tasks.Pleroma.Config.migrate_to_db("test/fixtures/config/temp.secret.exs")
|
|
||||||
assert Repo.aggregate(Pleroma.ConfigDB, :count, :id) > 0
|
|
||||||
|
|
||||||
conn = get(conn, "/api/pleroma/admin/config/migrate_from_db")
|
|
||||||
|
|
||||||
assert json_response(conn, 200) == %{}
|
|
||||||
assert Repo.all(Pleroma.ConfigDB) == []
|
|
||||||
end
|
|
||||||
|
|
||||||
test "returns error if configuration from database is off", %{conn: conn} do
|
|
||||||
initial = Pleroma.Config.get(:configurable_from_database)
|
|
||||||
on_exit(fn -> Pleroma.Config.put(:configurable_from_database, initial) end)
|
|
||||||
Pleroma.Config.put(:configurable_from_database, false)
|
|
||||||
|
|
||||||
conn = get(conn, "/api/pleroma/admin/config/migrate_from_db")
|
|
||||||
|
|
||||||
assert json_response(conn, 400) ==
|
|
||||||
"To use this endpoint you need to enable configuration from database."
|
|
||||||
|
|
||||||
assert Repo.all(Pleroma.ConfigDB) == []
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -238,7 +238,9 @@ test "reacting to a status with an emoji" do
|
||||||
assert reaction.data["actor"] == user.ap_id
|
assert reaction.data["actor"] == user.ap_id
|
||||||
assert reaction.data["content"] == "👍"
|
assert reaction.data["content"] == "👍"
|
||||||
|
|
||||||
# TODO: test error case.
|
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
|
||||||
|
|
||||||
|
{:error, _} = CommonAPI.react_with_emoji(activity.id, user, ".")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "unreacting to a status with an emoji" do
|
test "unreacting to a status with an emoji" do
|
||||||
|
@ -322,6 +324,21 @@ test "pin status", %{user: user, activity: activity} do
|
||||||
assert %User{pinned_activities: [^id]} = user
|
assert %User{pinned_activities: [^id]} = user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "pin poll", %{user: user} do
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(user, %{
|
||||||
|
"status" => "How is fediverse today?",
|
||||||
|
"poll" => %{"options" => ["Absolutely outstanding", "Not good"], "expires_in" => 20}
|
||||||
|
})
|
||||||
|
|
||||||
|
assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
|
||||||
|
|
||||||
|
id = activity.id
|
||||||
|
user = refresh_record(user)
|
||||||
|
|
||||||
|
assert %User{pinned_activities: [^id]} = user
|
||||||
|
end
|
||||||
|
|
||||||
test "unlisted statuses can be pinned", %{user: user} do
|
test "unlisted statuses can be pinned", %{user: user} do
|
||||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!", "visibility" => "unlisted"})
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!", "visibility" => "unlisted"})
|
||||||
assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
|
assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
|
||||||
|
@ -534,6 +551,50 @@ test "also unsubscribes a user" do
|
||||||
|
|
||||||
refute User.subscribed_to?(follower, followed)
|
refute User.subscribed_to?(follower, followed)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "cancels a pending follow for a local user" do
|
||||||
|
follower = insert(:user)
|
||||||
|
followed = insert(:user, locked: true)
|
||||||
|
|
||||||
|
assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
|
||||||
|
CommonAPI.follow(follower, followed)
|
||||||
|
|
||||||
|
assert User.get_follow_state(follower, followed) == "pending"
|
||||||
|
assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
|
||||||
|
assert User.get_follow_state(follower, followed) == nil
|
||||||
|
|
||||||
|
assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
|
||||||
|
Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
data: %{
|
||||||
|
"type" => "Undo",
|
||||||
|
"object" => %{"type" => "Follow", "state" => "cancelled"}
|
||||||
|
}
|
||||||
|
} = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "cancels a pending follow for a remote user" do
|
||||||
|
follower = insert(:user)
|
||||||
|
followed = insert(:user, locked: true, local: false, ap_enabled: true)
|
||||||
|
|
||||||
|
assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
|
||||||
|
CommonAPI.follow(follower, followed)
|
||||||
|
|
||||||
|
assert User.get_follow_state(follower, followed) == "pending"
|
||||||
|
assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
|
||||||
|
assert User.get_follow_state(follower, followed) == nil
|
||||||
|
|
||||||
|
assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
|
||||||
|
Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
data: %{
|
||||||
|
"type" => "Undo",
|
||||||
|
"object" => %{"type" => "Follow", "state" => "cancelled"}
|
||||||
|
}
|
||||||
|
} = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "accept_follow_request/2" do
|
describe "accept_follow_request/2" do
|
||||||
|
|
|
@ -269,7 +269,7 @@ test "update fields", %{conn: conn} do
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
|
||||||
assert account_data["fields"] == [
|
assert account_data["fields"] == [
|
||||||
%{"name" => "foo", "value" => "bar"},
|
%{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "bar"},
|
||||||
%{"name" => "link", "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)}
|
%{"name" => "link", "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -297,7 +297,7 @@ test "update fields", %{conn: conn} do
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
|
||||||
assert account["fields"] == [
|
assert account["fields"] == [
|
||||||
%{"name" => "foo", "value" => "bar"},
|
%{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "bar"},
|
||||||
%{"name" => "link", "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)}
|
%{"name" => "link", "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -457,6 +457,16 @@ test "following / unfollowing a user", %{conn: conn} do
|
||||||
assert id == to_string(other_user.id)
|
assert id == to_string(other_user.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "cancelling follow request", %{conn: conn} do
|
||||||
|
%{id: other_user_id} = insert(:user, %{locked: true})
|
||||||
|
|
||||||
|
assert %{"id" => ^other_user_id, "following" => false, "requested" => true} =
|
||||||
|
conn |> post("/api/v1/accounts/#{other_user_id}/follow") |> json_response(:ok)
|
||||||
|
|
||||||
|
assert %{"id" => ^other_user_id, "following" => false, "requested" => false} =
|
||||||
|
conn |> post("/api/v1/accounts/#{other_user_id}/unfollow") |> json_response(:ok)
|
||||||
|
end
|
||||||
|
|
||||||
test "following without reblogs" do
|
test "following without reblogs" do
|
||||||
%{conn: conn} = oauth_access(["follow", "read:statuses"])
|
%{conn: conn} = oauth_access(["follow", "read:statuses"])
|
||||||
followed = insert(:user)
|
followed = insert(:user)
|
||||||
|
@ -668,6 +678,7 @@ test "returns error when user already registred", %{conn: conn, valid_params: va
|
||||||
end
|
end
|
||||||
|
|
||||||
test "rate limit", %{conn: conn} do
|
test "rate limit", %{conn: conn} do
|
||||||
|
Pleroma.Config.put([Pleroma.Plugs.RemoteIp, :enabled], true)
|
||||||
app_token = insert(:oauth_token, user: nil)
|
app_token = insert(:oauth_token, user: nil)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
|
|
|
@ -370,6 +370,11 @@ test "posting a poll", %{conn: conn} do
|
||||||
|
|
||||||
assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
|
assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
|
||||||
refute response["poll"]["expred"]
|
refute response["poll"]["expred"]
|
||||||
|
|
||||||
|
question = Object.get_by_id(response["poll"]["id"])
|
||||||
|
|
||||||
|
# closed contains utc timezone
|
||||||
|
assert question.data["closed"] =~ "Z"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "option limit is enforced", %{conn: conn} do
|
test "option limit is enforced", %{conn: conn} do
|
||||||
|
|
|
@ -368,10 +368,10 @@ test "returns the settings store if the requesting user is the represented user
|
||||||
assert result.pleroma[:settings_store] == nil
|
assert result.pleroma[:settings_store] == nil
|
||||||
end
|
end
|
||||||
|
|
||||||
test "sanitizes display names" do
|
test "doesn't sanitize display names" do
|
||||||
user = insert(:user, name: "<marquee> username </marquee>")
|
user = insert(:user, name: "<marquee> username </marquee>")
|
||||||
result = AccountView.render("show.json", %{user: user})
|
result = AccountView.render("show.json", %{user: user})
|
||||||
refute result.display_name == "<marquee> username </marquee>"
|
assert result.display_name == "<marquee> username </marquee>"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "never display nil user follow counts" do
|
test "never display nil user follow counts" do
|
||||||
|
|
|
@ -135,7 +135,7 @@ test "Move notification" do
|
||||||
NotificationView.render("index.json", %{notifications: [notification], for: follower})
|
NotificationView.render("index.json", %{notifications: [notification], for: follower})
|
||||||
end
|
end
|
||||||
|
|
||||||
test "EmojiReaction notification" do
|
test "EmojiReact notification" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,15 @@ test "has an emoji reaction list" do
|
||||||
status = StatusView.render("show.json", activity: activity)
|
status = StatusView.render("show.json", activity: activity)
|
||||||
|
|
||||||
assert status[:pleroma][:emoji_reactions] == [
|
assert status[:pleroma][:emoji_reactions] == [
|
||||||
%{emoji: "☕", count: 2},
|
%{emoji: "☕", count: 2, reacted: false},
|
||||||
%{emoji: "🍵", count: 1}
|
%{emoji: "🍵", count: 1, reacted: false}
|
||||||
|
]
|
||||||
|
|
||||||
|
status = StatusView.render("show.json", activity: activity, for: user)
|
||||||
|
|
||||||
|
assert status[:pleroma][:emoji_reactions] == [
|
||||||
|
%{emoji: "☕", count: 2, reacted: true},
|
||||||
|
%{emoji: "🍵", count: 1, reacted: false}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
|
||||||
use Pleroma.Web.ConnCase
|
use Pleroma.Web.ConnCase
|
||||||
|
|
||||||
import Tesla.Mock
|
import Tesla.Mock
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
@emoji_dir_path Path.join(
|
@emoji_dir_path Path.join(
|
||||||
|
|
|
@ -25,9 +25,14 @@ test "POST /api/v1/pleroma/statuses/:id/react_with_emoji", %{conn: conn} do
|
||||||
|> assign(:user, other_user)
|
|> assign(:user, other_user)
|
||||||
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"]))
|
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"]))
|
||||||
|> post("/api/v1/pleroma/statuses/#{activity.id}/react_with_emoji", %{"emoji" => "☕"})
|
|> post("/api/v1/pleroma/statuses/#{activity.id}/react_with_emoji", %{"emoji" => "☕"})
|
||||||
|
|> json_response(200)
|
||||||
|
|
||||||
assert %{"id" => id} = json_response(result, 200)
|
assert %{"id" => id} = result
|
||||||
assert to_string(activity.id) == id
|
assert to_string(activity.id) == id
|
||||||
|
|
||||||
|
assert result["pleroma"]["emoji_reactions"] == [
|
||||||
|
%{"emoji" => "☕", "count" => 1, "reacted" => true}
|
||||||
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "POST /api/v1/pleroma/statuses/:id/unreact_with_emoji", %{conn: conn} do
|
test "POST /api/v1/pleroma/statuses/:id/unreact_with_emoji", %{conn: conn} do
|
||||||
|
@ -54,6 +59,7 @@ test "POST /api/v1/pleroma/statuses/:id/unreact_with_emoji", %{conn: conn} do
|
||||||
test "GET /api/v1/pleroma/statuses/:id/emoji_reactions_by", %{conn: conn} do
|
test "GET /api/v1/pleroma/statuses/:id/emoji_reactions_by", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
doomed_user = insert(:user)
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"})
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"})
|
||||||
|
|
||||||
|
@ -65,14 +71,29 @@ test "GET /api/v1/pleroma/statuses/:id/emoji_reactions_by", %{conn: conn} do
|
||||||
assert result == []
|
assert result == []
|
||||||
|
|
||||||
{:ok, _, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
|
{:ok, _, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
|
||||||
|
{:ok, _, _} = CommonAPI.react_with_emoji(activity.id, doomed_user, "🎅")
|
||||||
|
|
||||||
|
User.perform(:delete, doomed_user)
|
||||||
|
|
||||||
result =
|
result =
|
||||||
conn
|
conn
|
||||||
|> get("/api/v1/pleroma/statuses/#{activity.id}/emoji_reactions_by")
|
|> get("/api/v1/pleroma/statuses/#{activity.id}/emoji_reactions_by")
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
|
||||||
[%{"emoji" => "🎅", "count" => 1, "accounts" => [represented_user]}] = result
|
[%{"emoji" => "🎅", "count" => 1, "accounts" => [represented_user], "reacted" => false}] =
|
||||||
|
result
|
||||||
|
|
||||||
assert represented_user["id"] == other_user.id
|
assert represented_user["id"] == other_user.id
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> assign(:user, other_user)
|
||||||
|
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:statuses"]))
|
||||||
|
|> get("/api/v1/pleroma/statuses/#{activity.id}/emoji_reactions_by")
|
||||||
|
|> json_response(200)
|
||||||
|
|
||||||
|
assert [%{"emoji" => "🎅", "count" => 1, "accounts" => [_represented_user], "reacted" => true}] =
|
||||||
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
test "/api/v1/pleroma/conversations/:id" do
|
test "/api/v1/pleroma/conversations/:id" do
|
||||||
|
|
|
@ -85,4 +85,19 @@ test "respect only first title tag on the page" do
|
||||||
image: image_path
|
image: image_path
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "takes first founded title in html head if there is html markup error" do
|
||||||
|
html = File.read!("test/fixtures/nypd-facial-recognition-children-teenagers4.html")
|
||||||
|
|
||||||
|
assert TwitterCard.parse(html, %{}) ==
|
||||||
|
{:ok,
|
||||||
|
%{
|
||||||
|
site: nil,
|
||||||
|
title:
|
||||||
|
"She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database. - The New York Times",
|
||||||
|
"app:id:googleplay": "com.nytimes.android",
|
||||||
|
"app:name:googleplay": "NYTimes",
|
||||||
|
"app:url:googleplay": "nytimes://reader/id/100000006583622"
|
||||||
|
}}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -65,6 +65,9 @@ test "it doesn't send notify to the 'user:notification' stream when a user is bl
|
||||||
blocked = insert(:user)
|
blocked = insert(:user)
|
||||||
{:ok, _user_relationship} = User.block(user, blocked)
|
{:ok, _user_relationship} = User.block(user, blocked)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => ":("})
|
||||||
|
{:ok, notif, _} = CommonAPI.favorite(activity.id, blocked)
|
||||||
|
|
||||||
task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end)
|
task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end)
|
||||||
|
|
||||||
Streamer.add_socket(
|
Streamer.add_socket(
|
||||||
|
@ -72,9 +75,6 @@ test "it doesn't send notify to the 'user:notification' stream when a user is bl
|
||||||
%{transport_pid: task.pid, assigns: %{user: user}}
|
%{transport_pid: task.pid, assigns: %{user: user}}
|
||||||
)
|
)
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{"status" => ":("})
|
|
||||||
{:ok, notif, _} = CommonAPI.favorite(activity.id, blocked)
|
|
||||||
|
|
||||||
Streamer.stream("user:notification", notif)
|
Streamer.stream("user:notification", notif)
|
||||||
Task.await(task)
|
Task.await(task)
|
||||||
end
|
end
|
||||||
|
@ -83,6 +83,11 @@ test "it doesn't send notify to the 'user:notification' stream when a thread is
|
||||||
user: user
|
user: user
|
||||||
} do
|
} do
|
||||||
user2 = insert(:user)
|
user2 = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"})
|
||||||
|
{:ok, activity} = CommonAPI.add_mute(user, activity)
|
||||||
|
{:ok, notif, _} = CommonAPI.favorite(activity.id, user2)
|
||||||
|
|
||||||
task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end)
|
task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end)
|
||||||
|
|
||||||
Streamer.add_socket(
|
Streamer.add_socket(
|
||||||
|
@ -90,9 +95,6 @@ test "it doesn't send notify to the 'user:notification' stream when a thread is
|
||||||
%{transport_pid: task.pid, assigns: %{user: user}}
|
%{transport_pid: task.pid, assigns: %{user: user}}
|
||||||
)
|
)
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"})
|
|
||||||
{:ok, activity} = CommonAPI.add_mute(user, activity)
|
|
||||||
{:ok, notif, _} = CommonAPI.favorite(activity.id, user2)
|
|
||||||
Streamer.stream("user:notification", notif)
|
Streamer.stream("user:notification", notif)
|
||||||
Task.await(task)
|
Task.await(task)
|
||||||
end
|
end
|
||||||
|
@ -101,6 +103,11 @@ test "it doesn't send notify to the 'user:notification' stream' when a domain is
|
||||||
user: user
|
user: user
|
||||||
} do
|
} do
|
||||||
user2 = insert(:user, %{ap_id: "https://hecking-lewd-place.com/user/meanie"})
|
user2 = insert(:user, %{ap_id: "https://hecking-lewd-place.com/user/meanie"})
|
||||||
|
|
||||||
|
{:ok, user} = User.block_domain(user, "hecking-lewd-place.com")
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"})
|
||||||
|
{:ok, notif, _} = CommonAPI.favorite(activity.id, user2)
|
||||||
|
|
||||||
task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end)
|
task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end)
|
||||||
|
|
||||||
Streamer.add_socket(
|
Streamer.add_socket(
|
||||||
|
@ -108,10 +115,6 @@ test "it doesn't send notify to the 'user:notification' stream' when a domain is
|
||||||
%{transport_pid: task.pid, assigns: %{user: user}}
|
%{transport_pid: task.pid, assigns: %{user: user}}
|
||||||
)
|
)
|
||||||
|
|
||||||
{:ok, user} = User.block_domain(user, "hecking-lewd-place.com")
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"})
|
|
||||||
{:ok, notif, _} = CommonAPI.favorite(activity.id, user2)
|
|
||||||
|
|
||||||
Streamer.stream("user:notification", notif)
|
Streamer.stream("user:notification", notif)
|
||||||
Task.await(task)
|
Task.await(task)
|
||||||
end
|
end
|
||||||
|
@ -267,6 +270,8 @@ test "it doesn't send messages involving blocked users" do
|
||||||
blocked_user = insert(:user)
|
blocked_user = insert(:user)
|
||||||
{:ok, _user_relationship} = User.block(user, blocked_user)
|
{:ok, _user_relationship} = User.block(user, blocked_user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(blocked_user, %{"status" => "Test"})
|
||||||
|
|
||||||
task =
|
task =
|
||||||
Task.async(fn ->
|
Task.async(fn ->
|
||||||
refute_receive {:text, _}, 1_000
|
refute_receive {:text, _}, 1_000
|
||||||
|
@ -277,8 +282,6 @@ test "it doesn't send messages involving blocked users" do
|
||||||
user: user
|
user: user
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(blocked_user, %{"status" => "Test"})
|
|
||||||
|
|
||||||
topics = %{
|
topics = %{
|
||||||
"public" => [fake_socket]
|
"public" => [fake_socket]
|
||||||
}
|
}
|
||||||
|
@ -335,6 +338,12 @@ test "it doesn't send unwanted DMs to list" do
|
||||||
{:ok, list} = List.create("Test", user_a)
|
{:ok, list} = List.create("Test", user_a)
|
||||||
{:ok, list} = List.follow(list, user_b)
|
{:ok, list} = List.follow(list, user_b)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(user_b, %{
|
||||||
|
"status" => "@#{user_c.nickname} Test",
|
||||||
|
"visibility" => "direct"
|
||||||
|
})
|
||||||
|
|
||||||
task =
|
task =
|
||||||
Task.async(fn ->
|
Task.async(fn ->
|
||||||
refute_receive {:text, _}, 1_000
|
refute_receive {:text, _}, 1_000
|
||||||
|
@ -345,12 +354,6 @@ test "it doesn't send unwanted DMs to list" do
|
||||||
user: user_a
|
user: user_a
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, activity} =
|
|
||||||
CommonAPI.post(user_b, %{
|
|
||||||
"status" => "@#{user_c.nickname} Test",
|
|
||||||
"visibility" => "direct"
|
|
||||||
})
|
|
||||||
|
|
||||||
topics = %{
|
topics = %{
|
||||||
"list:#{list.id}" => [fake_socket]
|
"list:#{list.id}" => [fake_socket]
|
||||||
}
|
}
|
||||||
|
@ -367,6 +370,12 @@ test "it doesn't send unwanted private posts to list" do
|
||||||
{:ok, list} = List.create("Test", user_a)
|
{:ok, list} = List.create("Test", user_a)
|
||||||
{:ok, list} = List.follow(list, user_b)
|
{:ok, list} = List.follow(list, user_b)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(user_b, %{
|
||||||
|
"status" => "Test",
|
||||||
|
"visibility" => "private"
|
||||||
|
})
|
||||||
|
|
||||||
task =
|
task =
|
||||||
Task.async(fn ->
|
Task.async(fn ->
|
||||||
refute_receive {:text, _}, 1_000
|
refute_receive {:text, _}, 1_000
|
||||||
|
@ -377,12 +386,6 @@ test "it doesn't send unwanted private posts to list" do
|
||||||
user: user_a
|
user: user_a
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, activity} =
|
|
||||||
CommonAPI.post(user_b, %{
|
|
||||||
"status" => "Test",
|
|
||||||
"visibility" => "private"
|
|
||||||
})
|
|
||||||
|
|
||||||
topics = %{
|
topics = %{
|
||||||
"list:#{list.id}" => [fake_socket]
|
"list:#{list.id}" => [fake_socket]
|
||||||
}
|
}
|
||||||
|
@ -401,6 +404,12 @@ test "it sends wanted private posts to list" do
|
||||||
{:ok, list} = List.create("Test", user_a)
|
{:ok, list} = List.create("Test", user_a)
|
||||||
{:ok, list} = List.follow(list, user_b)
|
{:ok, list} = List.follow(list, user_b)
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
CommonAPI.post(user_b, %{
|
||||||
|
"status" => "Test",
|
||||||
|
"visibility" => "private"
|
||||||
|
})
|
||||||
|
|
||||||
task =
|
task =
|
||||||
Task.async(fn ->
|
Task.async(fn ->
|
||||||
assert_receive {:text, _}, 1_000
|
assert_receive {:text, _}, 1_000
|
||||||
|
@ -411,12 +420,6 @@ test "it sends wanted private posts to list" do
|
||||||
user: user_a
|
user: user_a
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, activity} =
|
|
||||||
CommonAPI.post(user_b, %{
|
|
||||||
"status" => "Test",
|
|
||||||
"visibility" => "private"
|
|
||||||
})
|
|
||||||
|
|
||||||
Streamer.add_socket(
|
Streamer.add_socket(
|
||||||
"list:#{list.id}",
|
"list:#{list.id}",
|
||||||
fake_socket
|
fake_socket
|
||||||
|
@ -433,6 +436,9 @@ test "it doesn't send muted reblogs" do
|
||||||
user3 = insert(:user)
|
user3 = insert(:user)
|
||||||
CommonAPI.hide_reblogs(user1, user2)
|
CommonAPI.hide_reblogs(user1, user2)
|
||||||
|
|
||||||
|
{:ok, create_activity} = CommonAPI.post(user3, %{"status" => "I'm kawen"})
|
||||||
|
{:ok, announce_activity, _} = CommonAPI.repeat(create_activity.id, user2)
|
||||||
|
|
||||||
task =
|
task =
|
||||||
Task.async(fn ->
|
Task.async(fn ->
|
||||||
refute_receive {:text, _}, 1_000
|
refute_receive {:text, _}, 1_000
|
||||||
|
@ -443,9 +449,6 @@ test "it doesn't send muted reblogs" do
|
||||||
user: user1
|
user: user1
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, create_activity} = CommonAPI.post(user3, %{"status" => "I'm kawen"})
|
|
||||||
{:ok, announce_activity, _} = CommonAPI.repeat(create_activity.id, user2)
|
|
||||||
|
|
||||||
topics = %{
|
topics = %{
|
||||||
"public" => [fake_socket]
|
"public" => [fake_socket]
|
||||||
}
|
}
|
||||||
|
@ -455,6 +458,34 @@ test "it doesn't send muted reblogs" do
|
||||||
Task.await(task)
|
Task.await(task)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it does send non-reblog notification for reblog-muted actors" do
|
||||||
|
user1 = insert(:user)
|
||||||
|
user2 = insert(:user)
|
||||||
|
user3 = insert(:user)
|
||||||
|
CommonAPI.hide_reblogs(user1, user2)
|
||||||
|
|
||||||
|
{:ok, create_activity} = CommonAPI.post(user3, %{"status" => "I'm kawen"})
|
||||||
|
{:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, user2)
|
||||||
|
|
||||||
|
task =
|
||||||
|
Task.async(fn ->
|
||||||
|
assert_receive {:text, _}, 1_000
|
||||||
|
end)
|
||||||
|
|
||||||
|
fake_socket = %StreamerSocket{
|
||||||
|
transport_pid: task.pid,
|
||||||
|
user: user1
|
||||||
|
}
|
||||||
|
|
||||||
|
topics = %{
|
||||||
|
"public" => [fake_socket]
|
||||||
|
}
|
||||||
|
|
||||||
|
Worker.push_to_socket(topics, "public", favorite_activity)
|
||||||
|
|
||||||
|
Task.await(task)
|
||||||
|
end
|
||||||
|
|
||||||
test "it doesn't send posts from muted threads" do
|
test "it doesn't send posts from muted threads" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
user2 = insert(:user)
|
user2 = insert(:user)
|
||||||
|
|
Loading…
Reference in New Issue